September 2026

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 支持三种选择模式:singlemultiplerange

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

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

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
October 2026

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>
  );
}

带最小/最大约束的范围

使用 minmax 属性约束范围长度(以天/夜为单位)。

September 2026
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
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 locale
  • className — 额外的 CSS 类

完整 API 参见

react-day-picker 文档

与 react-day-picker 的差异

为了与其他 Kumo 表单组件保持一致,DatePicker 使用 onChange 而非 react-day-picker 的 onSelect。 类型推断保持完整——TypeScript 会根据 mode 属性正确窄化回调签名。