组件
日期选择器 Date Picker
在紧凑弹层中选择、格式化和清除日期。
基础示例
Loading…
pnpm dlx wui@latest add @wui/date-picker"use client"
import * as React from "react"
import { CalendarIcon, XIcon } from "lucide-react"
import { Popover as PopoverPrimitive } from "radix-ui"
import { Calendar } from "@/components/ui/calendar"
import { cn } from "@/lib/utils"
export interface DatePickerProps extends Omit<
React.ComponentProps<"button">,
"value" | "defaultValue" | "onChange"
> {
/** Selected date. */
value?: Date
/** Initially selected date in uncontrolled mode. */
defaultValue?: Date
/** Called after a date is selected or cleared. */
onValueChange?: (date: Date | undefined) => void
/** Placeholder shown without a value. @default "选择日期" */
placeholder?: string
/** Date formatting locale. @default "zh-CN" */
locale?: string
/** Intl date formatting options. */
formatOptions?: Intl.DateTimeFormatOptions
/** Allow clearing the current selection. @default true */
clearable?: boolean
/** Earliest selectable day. */
min?: Date
/** Latest selectable day. */
max?: Date
/** Custom disabled-day matcher. */
disabledDate?: (date: Date) => boolean
}
/** A popover date picker composed from the WUI Calendar. */
function DatePicker({
className,
value,
defaultValue,
onValueChange,
placeholder = "选择日期",
locale = "zh-CN",
formatOptions,
clearable = true,
min,
max,
disabledDate,
disabled,
onKeyDown,
...props
}: DatePickerProps) {
const [open, setOpen] = React.useState(false)
const [internalValue, setInternalValue] = React.useState(defaultValue)
const selected = value ?? internalValue
function update(next: Date | undefined) {
if (value === undefined) setInternalValue(next)
onValueChange?.(next)
}
const label = selected
? new Intl.DateTimeFormat(
locale,
formatOptions ?? { year: "numeric", month: "long", day: "numeric" }
).format(selected)
: placeholder
return (
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
<PopoverPrimitive.Trigger asChild>
<button
type="button"
data-slot="date-picker"
data-placeholder={!selected || undefined}
className={cn(
"bg-background shadow-xs hover:border-foreground/25 focus-visible:border-ring focus-visible:ring-ring/30 data-[placeholder=true]:text-muted-foreground group flex h-9 min-w-56 items-center gap-2 rounded-md border px-3 text-left text-sm outline-none transition-[border-color,box-shadow] focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50",
className
)}
disabled={disabled}
onKeyDown={(event) => {
onKeyDown?.(event)
if (
!event.defaultPrevented &&
selected &&
clearable &&
(event.key === "Delete" || event.key === "Backspace")
) {
event.preventDefault()
update(undefined)
}
}}
{...props}
>
<CalendarIcon className="text-muted-foreground size-4 shrink-0" />
<span className="min-w-0 flex-1 truncate">{label}</span>
{selected && clearable ? (
<span
aria-hidden
className="text-muted-foreground hover:bg-accent hover:text-foreground -mr-1 flex size-6 items-center justify-center rounded-sm opacity-0 transition-opacity focus:opacity-100 group-hover:opacity-100"
onClick={(event) => {
event.stopPropagation()
update(undefined)
}}
>
<XIcon className="size-3.5" />
</span>
) : null}
</button>
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
sideOffset={6}
align="start"
className="bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 z-50 rounded-lg border shadow-md outline-none"
>
<Calendar
value={selected}
defaultMonth={selected}
min={min}
max={max}
disabled={disabledDate}
locale={locale}
onValueChange={(date) => {
update(date)
setOpen(false)
}}
/>
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
)
}
export { DatePicker }
组件作用
DatePicker 用于表单中的单日期输入,并复用 Calendar 的日期规则与键盘能力。需要连续日期、多个日期或时间范围时,应组合专门的范围选择器,而不是在单值接口中塞入数组。
组件属性
| Prop | Type | Default | Description |
|---|---|---|---|
| value | Date | — | Selected date. |
| defaultValue | Date | — | Initially selected date in uncontrolled mode. |
| onValueChange | ((date: Date) => void) | — | Called after a date is selected or cleared. |
| placeholder | string | 选择日期 | Placeholder shown without a value. |
| locale | string | zh-CN | Date formatting locale. |
| formatOptions | DateTimeFormatOptions | — | Intl date formatting options. |
| clearable | boolean | true | Allow clearing the current selection. |
| min | Date | — | Earliest selectable day. |
| max | Date | — | Latest selectable day. |
| disabledDate | ((date: Date) => boolean) | — | Custom disabled-day matcher. |
| disabled | boolean | — |
事件
onValueChange(date | undefined) 在选择或清空日期后触发。选中日期后弹层自动关闭;点击清除按钮不会打开弹层。
扩展使用
使用 locale 与 formatOptions 控制触发器文案;min、max 和 disabledDate 会原样传递给内部 Calendar。表单校验信息应放在触发器下方,而不是放进弹层。