September 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} />;
}示例
单个日期选择
选择单个日期。这是日期选择器最常见的用法。
September 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 限制选择数量。
September 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} 配合可实现
并排双月视图。
September 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
October 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 属性约束范围长度(以天/夜为单位)。
September 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 展示用量信息。
September 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 localeclassName— 额外的 CSS 类
完整 API 参见
react-day-picker 文档
。
与 react-day-picker 的差异
为了与其他 Kumo 表单组件保持一致,DatePicker 使用 onChange 而非 react-day-picker 的 onSelect。
类型推断保持完整——TypeScript 会根据 mode 属性正确窄化回调签名。