DateRangePicker
WhereUIช่องเลือกช่วงวันที่ (วันเริ่มต้น–วันสิ้นสุด) พร้อมปฏิทิน popover แสดงภาษาไทยและปี พ.ศ. ปิดอัตโนมัติเมื่อเลือกครบทั้งสองวัน
ยังไม่ได้เลือกช่วงวันลา
ติดตั้ง
Terminal
pnpm dlx shadcn@latest add @whereui/date-range-pickerต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก
การใช้งาน
import { DateRangePicker, type DateRangeValue } from "@/components/ui/date-range-picker"const [range, setRange] = React.useState<DateRangeValue | null>(null)<DateRangePicker value={range} onChange={setRange} />ตัวอย่างเพิ่มเติม
จำกัดช่วงวันที่และสถานะต่าง ๆ
ลาได้เฉพาะไตรมาสนี้ (min/max)
ปิดใช้งาน
Props
| prop | type | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
| value | { from: Date | null; to: Date | null } | null | — | ช่วงวันที่ที่เลือกอยู่ (from/to เป็น null = ยังไม่เลือกปลายด้านนั้น) |
| onChange | (range: { from: Date | null; to: Date | null }) => void | — | เรียกทุกครั้งที่ผู้ใช้คลิกวันในปฏิทิน |
| placeholder | string | "เลือกช่วงวันที่" | ข้อความแสดงเมื่อยังไม่เลือกวันเริ่มต้น |
| min | Date | — | วันต่ำสุดที่เลือกได้ |
| max | Date | — | วันสูงสุดที่เลือกได้ |
| buddhistEra | boolean | true | แสดงปีเป็น พ.ศ. (ค.ศ. + 543) เฉพาะการแสดงผล |
| disabled | boolean | — | ปิดการใช้งาน |
ข้อควรรู้
- ค่าที่รับ/ส่งเป็น Date จริงเสมอ ปีที่แสดงเป็น พ.ศ. เป็นเรื่องการแสดงผลเท่านั้น
- popover จะปิดเองเมื่อเลือกวันสิ้นสุดครบแล้ว
ซอร์สโค้ด
components/ui/date-range-picker.tsx
"use client"import * as React from "react"import { th } from "date-fns/locale"import { CalendarIcon } from "lucide-react"import { cn } from "cn"import type { Matcher } from "react-day-picker"import { Calendar } from "@/components/ui/calendar"import { formatThaiDate } from "@/components/ui/date-picker"import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"/** ค่าช่วงวันที่ — null = ยังไม่เลือกปลายด้านนั้น */export type DateRangeValue = { from: Date | null; to: Date | null }function DateRangePicker({ value, onChange, placeholder = "เลือกช่วงวันที่", disabled, min, max, buddhistEra = true, className, ...props}: Omit<React.ComponentProps<"button">, "value" | "onChange"> & { value?: DateRangeValue | null onChange?: (range: DateRangeValue) => void placeholder?: string /** วันต่ำสุดที่เลือกได้ */ min?: Date /** วันสูงสุดที่เลือกได้ */ max?: Date /** แสดงปีเป็น พ.ศ. (ค.ศ. + 543) */ buddhistEra?: boolean}) { const [open, setOpen] = React.useState(false) const from = value?.from ?? null const to = value?.to ?? null const disabledMatchers = React.useMemo(() => { const matchers: Matcher[] = [] if (min) matchers.push({ before: min }) if (max) matchers.push({ after: max }) return matchers.length ? matchers : undefined }, [min, max]) return ( <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger type="button" data-slot="date-range-picker" disabled={disabled} className={cn( "flex h-8 w-full min-w-0 items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground", !from && "text-muted-foreground", className )} {...props} > <span className="truncate"> {from ? `${formatThaiDate(from, buddhistEra, "short")} – ${ to ? formatThaiDate(to, buddhistEra, "short") : "…" }` : placeholder} </span> <CalendarIcon aria-hidden="true" /> </PopoverTrigger> <PopoverContent align="start" className="w-auto p-0"> <Calendar mode="range" numberOfMonths={1} locale={th} defaultMonth={from ?? undefined} selected={{ from: from ?? undefined, to: to ?? undefined }} onSelect={(range) => { onChange?.({ from: range?.from ?? null, to: range?.to ?? null }) if (range?.to) setOpen(false) }} disabled={disabledMatchers} formatters={{ formatCaption: (month) => `${new Intl.DateTimeFormat("th-TH", { month: "long" }).format( month )} ${month.getFullYear() + (buddhistEra ? 543 : 0)}`, }} /> </PopoverContent> </Popover> )}export { DateRangePicker }