October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Selected: None
import { useState } from "react";
import { DatePicker } from "@cloudflare/kumo";
/**
* Single date selection.
*/
export function DatePickerSingleDemo() {
const [date, setDate] = useState<Date | undefined>();
return (
<div className="flex flex-col gap-4">
<DatePicker
mode="single"
selected={date}
onChange={(d) => {
if (d) {
setDate(d);
}
}}
/>
<p className="text-sm text-kumo-subtle">
Selected: {date ? date.toLocaleDateString() : "None"}
</p>
</div>
);
}安装
批量导入
import { DatePicker, type DateRange } from "@cloudflare/kumo";细粒度导入
import {
DatePicker,
type DateRange,
} from "@cloudflare/kumo/components/date-picker";用法
DatePicker 支持三种选择模式:single、multiple 和 range。
import { useState } from "react";
import { DatePicker } from "@cloudflare/kumo";
export default function Example() {
const [date, setDate] = useState<Date>();
return <DatePicker mode="single" selected={date} onChange={setDate} />;
}示例
选择单个日期
选择单个日期,这是日期选择器最常见的用例。
October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Selected: None
import { useState } from "react";
import { DatePicker } from "@cloudflare/kumo";
/**
* Single date selection.
*/
export function DatePickerSingleDemo() {
const [date, setDate] = useState<Date | undefined>();
return (
<div className="flex flex-col gap-4">
<DatePicker
mode="single"
selected={date}
onChange={(d) => {
if (d) {
setDate(d);
}
}}
/>
<p className="text-sm text-kumo-subtle">
Selected: {date ? date.toLocaleDateString() : "None"}
</p>
</div>
);
}选择多个日期
选择多个不连续的日期。使用 max 限制可选数量。
October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Selected: 0 date(s)
import { useState } from "react";
import { DatePicker } from "@cloudflare/kumo";
/**
* Multiple date selection with a maximum of 5 dates.
*/
export function DatePickerMultipleDemo() {
const [dates, setDates] = useState<Date[] | undefined>();
return (
<div className="flex flex-col gap-4">
<DatePicker
mode="multiple"
selected={dates}
onChange={setDates}
max={5}
/>
<p className="text-sm text-kumo-subtle">
Selected: {dates?.length ?? 0} date(s)
</p>
</div>
);
}选择日期范围
选择一段连续的日期范围。配合 numberOfMonths={2} 可实现并排显示。
多月视图会隐藏每个月之外的日期,避免日期在两个面板之间重复。
设置 showOutsideDays 可显示这些日期。
October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
November 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Range: None
import { useState } from "react";
import { DatePicker, DateRange } from "@cloudflare/kumo";
/**
* Date range selection with two months displayed.
*/
export function DatePickerRangeDemo() {
const [range, setRange] = useState<DateRange | undefined>();
return (
<div className="flex flex-col gap-4">
<DatePicker
mode="range"
selected={range}
onChange={setRange}
numberOfMonths={2}
/>
<p className="text-sm text-kumo-subtle">
Range:{" "}
{range?.from
? `${range.from.toLocaleDateString()} - ${range.to?.toLocaleDateString() ?? "..."}`
: "None"}
</p>
</div>
);
}带最小/最大限制的范围
使用 min 和 max 属性限制范围长度 (以天/晚计)。
October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
import { useState } from "react";
import { DatePicker, DateRange } from "@cloudflare/kumo";
/**
* Date range with minimum 3 nights and maximum 7 nights.
*/
export function DatePickerRangeMinMaxDemo() {
const [range, setRange] = useState<DateRange | undefined>();
return (
<div className="flex flex-col gap-4">
<DatePicker
mode="range"
selected={range}
onChange={setRange}
min={3}
max={7}
footer={
<span className="text-xs text-kumo-subtle">Select 3-7 nights</span>
}
/>
</div>
);
}搭配 Popover
与 Popover 组件组合,创建下拉式 日期选择器。
import { useState } from "react";
import { DatePicker, Popover, Button } from "@cloudflare/kumo";
import { CalendarDotsIcon } from "@phosphor-icons/react";
/**
* Date picker composed with a Popover for dropdown behavior.
*/
export function DatePickerPopoverDemo() {
const [date, setDate] = useState<Date | undefined>();
return (
<Popover>
<Popover.Trigger
render={<Button variant="outline" icon={CalendarDotsIcon} />}
>
{date ? date.toLocaleDateString() : "Pick a date"}
</Popover.Trigger>
<Popover.Content className="p-3">
<DatePicker mode="single" selected={date} onChange={setDate} />
</Popover.Content>
</Popover>
);
}带 Popover 的日期范围
放在 Popover 中、显示两个月的日期范围选择器。
import { useState } from "react";
import { DatePicker, Popover, Button, DateRange } from "@cloudflare/kumo";
import { CalendarDotsIcon } from "@phosphor-icons/react";
/**
* Date range picker composed with a Popover for dropdown behavior.
*/
export function DatePickerRangePopoverDemo() {
const [range, setRange] = useState<DateRange | undefined>();
const formatRange = () => {
if (!range?.from) return "Select dates";
if (!range.to) return range.from.toLocaleDateString();
return `${range.from.toLocaleDateString()} – ${range.to.toLocaleDateString()}`;
};
return (
<Popover>
<Popover.Trigger
render={<Button variant="outline" icon={CalendarDotsIcon} />}
>
{formatRange()}
</Popover.Trigger>
<Popover.Content className="p-3">
<DatePicker
mode="range"
selected={range}
onChange={setRange}
numberOfMonths={2}
/>
</Popover.Content>
</Popover>
);
}带预设选项的日期范围
将日期选择器与预设选项结合,实现快速选择。
import { useState } from "react";
import { DatePicker, Popover, Button, DateRange } from "@cloudflare/kumo";
import { CalendarDotsIcon } from "@phosphor-icons/react";
/**
* Date range picker with preset options in a popover.
*/
export function DatePickerRangeWithPresetsDemo() {
const [range, setRange] = useState<DateRange | undefined>();
const [month, setMonth] = useState<Date>(new Date());
const today = new Date();
const presets = [
{
label: "Today",
range: { from: today, to: today },
},
{
label: "Last 7 days",
range: {
from: new Date(today.getTime() - 6 * 24 * 60 * 60 * 1000),
to: today,
},
},
{
label: "Last 30 days",
range: {
from: new Date(today.getTime() - 29 * 24 * 60 * 60 * 1000),
to: today,
},
},
{
label: "Last 90 days",
range: {
from: new Date(today.getTime() - 89 * 24 * 60 * 60 * 1000),
to: today,
},
},
{
label: "This month",
range: {
from: new Date(today.getFullYear(), today.getMonth(), 1),
to: new Date(today.getFullYear(), today.getMonth() + 1, 0),
},
},
{
label: "Last month",
range: {
from: new Date(today.getFullYear(), today.getMonth() - 1, 1),
to: new Date(today.getFullYear(), today.getMonth(), 0),
},
},
];
const handlePresetClick = (preset: { range: DateRange }) => {
setRange(preset.range);
// Navigate calendar to show the start of the range
if (preset.range.from) {
setMonth(preset.range.from);
}
};
const isPresetActive = (preset: { range: DateRange }) => {
if (!range?.from || !range?.to || !preset.range.from || !preset.range.to)
return false;
// Compare dates only (ignore time)
const sameFrom =
range.from.toDateString() === preset.range.from.toDateString();
const sameTo = range.to.toDateString() === preset.range.to.toDateString();
return sameFrom && sameTo;
};
const formatRange = () => {
if (!range?.from) return "Select dates";
if (!range.to) return range.from.toLocaleDateString();
return `${range.from.toLocaleDateString()} – ${range.to.toLocaleDateString()}`;
};
return (
<Popover>
<Popover.Trigger
render={<Button variant="outline" icon={CalendarDotsIcon} />}
>
{formatRange()}
</Popover.Trigger>
<Popover.Content className="p-0">
<div className="flex">
<div className="flex flex-col gap-1 border-r border-kumo-hairline p-2 text-sm">
{presets.map((preset) => {
const isActive = isPresetActive(preset);
return (
<button
key={preset.label}
type="button"
onClick={() => handlePresetClick(preset)}
className={`rounded-md px-3 py-1.5 text-left whitespace-nowrap ${
isActive
? "bg-kumo-bg-inverse text-kumo-text-inverse"
: "text-kumo-subtle hover:bg-kumo-control"
}`}
>
{preset.label}
</button>
);
})}
</div>
<div className="p-3">
<DatePicker
mode="range"
selected={range}
onChange={setRange}
month={month}
onMonthChange={setMonth}
numberOfMonths={2}
/>
</div>
</div>
</Popover.Content>
</Popover>
);
}禁用日期与使用限制
使用 disabled 属性使某些日期不可选,
使用 footer 显示使用说明。
October 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
import { useState } from "react";
import { DatePicker } from "@cloudflare/kumo";
/**
* Date picker with disabled dates and a footer showing usage limits.
*/
export function DatePickerDisabledWithFooterDemo() {
const [dates, setDates] = useState<Date[] | undefined>();
const today = new Date();
// Example: some dates are already used/unavailable
const unavailableDates = [
new Date(today.getFullYear(), today.getMonth(), 5),
new Date(today.getFullYear(), today.getMonth(), 12),
new Date(today.getFullYear(), today.getMonth(), 18),
new Date(today.getFullYear(), today.getMonth(), 25),
];
const selectedCount = dates?.length ?? 0;
const maxDays = 5;
return (
<DatePicker
mode="multiple"
selected={dates}
onChange={setDates}
max={maxDays}
disabled={unavailableDates}
fixedWeeks
footer={
<p className="w-full pt-2 text-xs text-kumo-subtle">
{selectedCount}/{maxDays} days selected. Grayed dates are unavailable.
</p>
}
/>
);
}完整的 Popover 示例
下面是一个完整示例,展示如何将 DatePicker 与 Popover 组合:
import { useState } from "react";
import { DatePicker, Popover, Button } from "@cloudflare/kumo";
import { CalendarDotsIcon } from "@phosphor-icons/react";
export function DatePickerDropdown() {
const [date, setDate] = useState<Date>();
return (
<Popover>
<Popover.Trigger
render={<Button variant="outline" icon={CalendarDotsIcon} />}
>
{date ? date.toLocaleDateString() : "Pick a date"}
</Popover.Trigger>
<Popover.Content className="p-3">
<DatePicker
mode="single"
selected={date}
onChange={(d) => setDate(d)}
/>
</Popover.Content>
</Popover>
);
}API 参考
DatePicker 会将所有属性透传给
react-day-picker
。主要属性包括:
mode—"single" | "multiple" | "range"— 选择模式 (必填)selected— 当前选中的日期onChange— 选择变化时的回调numberOfMonths— 显示的月份数disabled— 不可选的日期min/max— 范围长度的最小/最大限制footer— 渲染在日历下方的内容locale— 用于国际化的 date-fns 语言环境className— 额外的 CSS 类
完整 API 请参阅
react-day-picker 文档
。
与 react-day-picker 的差异
为了与其他 Kumo 表单组件保持一致,DatePicker 使用 onChange 而不是 react-day-picker 的 onSelect。
完整的类型推断得以保留 —— TypeScript 会根据 mode 属性正确收窄回调签名。