Frontend Lab
← 回到所有模式
PE02⌨️ Input · 進階輸入日期

Date Picker

日期選擇

難度 ★★★★標籤 date · calendar · range3 min read

模式簡述

Date Picker:日期挑選。單日、區間、多選都要支援。i18n、時區、weekstart 都是雷區。

關閉|input only

Claude Code Prompt

💬 Claude Code Prompt
參考:PE02 date-picker
組件:DatePicker.tsx

Date Picker:日期挑選。單日、區間、多選都要支援。i18n、時區、weekstart 都是雷區。

限制:
- weekstart 寫死
- range 模式起訖點不對稱
- 時區漂移

完整程式碼

DatePicker.tsx
"use client";
import { useEffect, useState } from "react";
import { Calendar, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";

type State = "closed" | "open" | "selected" | "range";

export function DatePicker({ state = "open" }: { state?: State }) {
const [open, setOpen] = useState(state !== "closed");
useEffect(() => setOpen(state !== "closed"), [state]);

const selected = state === "selected" ? [15] : state === "range" ? [10, 11, 12, 13, 14, 15] : [];

const days = Array.from({ length: 30 }, (_, i) => i + 1);
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];

return (
  <div className="w-full max-w-xs">
    <div className="relative">
      <button
        onClick={() => setOpen((o) => !o)}
        className="flex w-full items-center justify-between rounded-md border border-border bg-background/50 px-3 py-2 text-xs"
      >
        <span className={selected.length ? "text-foreground" : "text-muted-foreground"}>
          {state === "range"
            ? "2026-05-10 → 2026-05-15"
            : state === "selected"
              ? "2026-05-15"
              : "選擇日期"}
        </span>
        <Calendar className="h-3.5 w-3.5 text-muted-foreground" />
      </button>
      {open && (
        <div className="absolute inset-x-0 top-full z-10 mt-1 rounded-md border border-border bg-background p-3 shadow-xl">
          <div className="mb-2 flex items-center justify-between">
            <button className="p-1 hover:text-lab-accent">
              <ChevronLeft className="h-3 w-3" />
            </button>
            <span className="text-xs font-medium">2026 年 5 月</span>
            <button className="p-1 hover:text-lab-accent">
              <ChevronRight className="h-3 w-3" />
            </button>
          </div>
          <div className="grid grid-cols-7 gap-0.5 text-center font-mono text-[10px] text-muted-foreground">
            {weekdays.map((w) => (
              <div key={w} className="py-0.5">{w}</div>
            ))}
            {days.map((d) => {
              const sel = selected.includes(d);
              const isStart = selected[0] === d;
              const isEnd = selected[selected.length - 1] === d;
              const inRange = selected.length > 1 && sel && !isStart && !isEnd;
              return (
                <button
                  key={d}
                  className={cn(
                    "rounded py-1 text-[11px] hover:bg-lab-accent/10",
                    sel && !inRange && "bg-lab-accent text-background",
                    inRange && "bg-lab-accent/20 text-foreground rounded-none",
                    isStart && selected.length > 1 && "rounded-r-none",
                    isEnd && selected.length > 1 && "rounded-l-none",
                  )}
                >
                  {d}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  </div>
);
}

何時用

  • ✅ 訂房、訂機票的入退日期
  • ✅ 報表時間區間篩選
  • ✅ 日程安排

何時別用

  • ❌ 只需要文字輸入(input type=date)
  • ❌ 未來/過去極遠日期(生日用 month/year drop)
  • ❌ 需要時、分、秒(合併 time picker)

我踩過的坑

  1. weekstart 寫死:美國週日開頭、台灣週一開頭。要可設定。

  2. range 模式起訖點不對稱:選了 start 後 hover 應該 preview range。

  3. 時區漂移:使用者選 5/10 但存了 UTC 04-30 23:00。日期類用 ISO date string,不是 ISO datetime。

相關模式

  • PA05
  • PE04

搜尋

按 ⌘K 隨時開啟