Date Picker
React date-timeDate input that opens a Calendar in a Popover. Handles parsing, formatting, min/max bounds, and disabled dates. Use the Range Calendar version for from/to selections.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://react.uipkge.dev/r/react/date-picker.json$ npx shadcn@latest add https://react.uipkge.dev/r/react/date-picker.json$ yarn dlx shadcn@latest add https://react.uipkge.dev/r/react/date-picker.json$ bunx shadcn@latest add https://react.uipkge.dev/r/react/date-picker.json
Or with the named registry:
npx shadcn@latest add @uipkge-react/date-picker
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
value Controlled value. ISO `YYYY-MM-DD` string or a `Date`. `null` = empty. | string | Date | null | — | optional |
defaultValue Uncontrolled initial value. | string | Date | null | — | optional |
onValueChange | (value: string | null) => void | — | optional |
placeholder | string | — | optional |
disabled | boolean | — | optional |
readOnly | boolean | — | optional |
clearable | boolean | — | optional |
format Format for the trigger label. String presets or Intl.DateTimeFormatOptions for full flexibility. | FormatValue | — | optional |
dateFormat Backward-compat alias for `format`. | FormatValue | — | optional |
locale | string | — | optional |
numberOfMonths | number | — | optional |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | optional |
fixedWeeks | boolean | — | optional |
minValue | string | Date | — | optional |
maxValue | string | Date | — | optional |
status Validation status — applies colored border to the trigger. | DatePickerStatus | — | optional |
size Size variant of the trigger input. | DatePickerSize | — | optional |
placement Placement of the popover relative to the trigger. | DatePickerPlacement | — | optional |
disabledDate Function to determine if a specific date should be disabled. | (current: Date) => boolean | — | optional |
triggerClassName | string | — | optional |
className | string | — | optional |
Dependencies
Files (2)
-
components/ui/date-picker/date-picker.tsx 7.2 kB
'use client' import * as React from 'react' import { Calendar as CalendarIcon, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Calendar } from '@/components/ui/calendar' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { cn } from '@/lib/utils' export type DatePickerStatus = 'error' | 'warning' export type DatePickerSize = 'small' | 'middle' | 'large' export type DatePickerPlacement = | 'top' | 'bottom' | 'left' | 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' type FormatValue = 'short' | 'medium' | 'long' | 'full' | Intl.DateTimeFormatOptions export interface DatePickerProps { /** Controlled value. ISO `YYYY-MM-DD` string or a `Date`. `null` = empty. */ value?: string | Date | null /** Uncontrolled initial value. */ defaultValue?: string | Date | null onValueChange?: (value: string | null) => void placeholder?: string disabled?: boolean readOnly?: boolean clearable?: boolean /** Format for the trigger label. String presets or Intl.DateTimeFormatOptions for full flexibility. */ format?: FormatValue /** Backward-compat alias for `format`. */ dateFormat?: FormatValue locale?: string numberOfMonths?: number weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 fixedWeeks?: boolean minValue?: string | Date maxValue?: string | Date /** Validation status — applies colored border to the trigger. */ status?: DatePickerStatus /** Size variant of the trigger input. */ size?: DatePickerSize /** Placement of the popover relative to the trigger. */ placement?: DatePickerPlacement /** Function to determine if a specific date should be disabled. */ disabledDate?: (current: Date) => boolean triggerClassName?: string className?: string } /** Parse an ISO `YYYY-MM-DD` (or full ISO) string / Date into a local Date. */ function coerce(v: string | Date | null | undefined): Date | null { if (!v) return null if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(v) if (m) { const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])) return Number.isNaN(d.getTime()) ? null : d } const d = new Date(v) return Number.isNaN(d.getTime()) ? null : d } /** Serialize a Date back to an ISO `YYYY-MM-DD` string (local, no time component). */ function toISODate(d: Date): string { const y = d.getFullYear() const mo = String(d.getMonth() + 1).padStart(2, '0') const da = String(d.getDate()).padStart(2, '0') return `${y}-${mo}-${da}` } const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>( ( { value, defaultValue = null, onValueChange, placeholder = 'Pick a date', disabled = false, readOnly = false, clearable = true, format = 'medium', dateFormat, locale = 'en-US', numberOfMonths = 1, weekStartsOn = 0, fixedWeeks = false, minValue, maxValue, status, size = 'middle', placement = 'bottomLeft', disabledDate, triggerClassName, className, }, ref, ) => { const [open, setOpen] = React.useState(false) const isControlled = value !== undefined const [internal, setInternal] = React.useState<Date | null>(() => coerce(defaultValue)) const selected = isControlled ? coerce(value) : internal const effectiveFormat = dateFormat ?? format const minDate = React.useMemo(() => coerce(minValue) ?? undefined, [minValue]) const maxDate = React.useMemo(() => coerce(maxValue) ?? undefined, [maxValue]) function fmtDate(d: Date) { const opts: Intl.DateTimeFormatOptions = typeof effectiveFormat === 'object' ? effectiveFormat : { dateStyle: effectiveFormat } return new Intl.DateTimeFormat(locale, opts).format(d) } const display = selected ? fmtDate(selected) : '' const hasValue = Boolean(selected) function commit(d: Date | null) { if (!isControlled) setInternal(d) onValueChange?.(d ? toISODate(d) : null) } function handleSelect(d: Date | undefined) { commit(d ?? null) if (d) setOpen(false) } function clear(event: React.MouseEvent) { event.stopPropagation() if (disabled || readOnly) return commit(null) } const placementMap: Record< DatePickerPlacement, { side: 'top' | 'bottom' | 'left' | 'right'; align: 'start' | 'center' | 'end' } > = { top: { side: 'top', align: 'center' }, bottom: { side: 'bottom', align: 'center' }, left: { side: 'left', align: 'center' }, right: { side: 'right', align: 'center' }, topLeft: { side: 'top', align: 'start' }, topRight: { side: 'top', align: 'end' }, bottomLeft: { side: 'bottom', align: 'start' }, bottomRight: { side: 'bottom', align: 'end' }, } const popoverPlacement = placementMap[placement] ?? { side: 'bottom', align: 'start' } const buttonSize = size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'default' const triggerClasses = cn( 'min-w-[240px]', 'justify-start gap-2 text-left font-normal', !hasValue && 'text-muted-foreground', status === 'error' && 'border-destructive focus-visible:ring-destructive', status === 'warning' && 'border-warning focus-visible:ring-warning', triggerClassName, className, ) const calendarDisabled = React.useMemo(() => { const matchers: ((d: Date) => boolean)[] = [] if (minDate) matchers.push((d) => d < minDate) if (maxDate) matchers.push((d) => d > maxDate) if (disabledDate) matchers.push(disabledDate) return matchers.length ? (d: Date) => matchers.some((m) => m(d)) : undefined }, [minDate, maxDate, disabledDate]) return ( <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button ref={ref} type="button" variant="outline" size={buttonSize} disabled={disabled} className={triggerClasses} data-uipkge="" data-slot="date-picker" > <CalendarIcon className="size-4" aria-hidden="true" /> <span className="flex-1 truncate">{display || placeholder}</span> {clearable && hasValue && !disabled && !readOnly && ( <button type="button" className="text-muted-foreground hover:text-foreground focus-visible:ring-ring -mr-1 inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none" aria-label="Clear date" onClick={clear} > <X className="size-3.5" /> </button> )} </Button> </PopoverTrigger> <PopoverContent className="w-auto p-0" side={popoverPlacement.side} align={popoverPlacement.align}> <Calendar mode="single" selected={selected ?? undefined} onSelect={handleSelect} numberOfMonths={numberOfMonths} weekStartsOn={weekStartsOn} fixedWeeks={fixedWeeks} disabled={calendarDisabled} /> </PopoverContent> </Popover> ) }, ) DatePicker.displayName = 'DatePicker' export { DatePicker } -
components/ui/date-picker/index.ts 0.1 kB
export { DatePicker, type DatePickerProps, type DatePickerStatus, type DatePickerSize, type DatePickerPlacement, } from './date-picker'
Raw manifest: https://react.uipkge.dev/r/react/date-picker.json