Date Picker
A popover calendar component allowing users to select dates with interactive month navigation.
datePicker.tsx
Core implementation of the DatePicker component.
ReactTailwindFormsUI Component
"use client";
import * as React from "react";
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
export interface DatePickerProps {
value?: Date;
onChange?: (date: Date) => void;
label?: React.ReactNode;
placeholder?: string;
disabled?: boolean;
}
export function DatePicker({
value,
onChange,
label,
placeholder = "Select date...",
disabled = false,
}: DatePickerProps) {
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(value);
const [isOpen, setIsOpen] = React.useState(false);
const [currentMonth, setCurrentMonth] = React.useState<Date>(value || new Date());
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (value !== undefined) {
setSelectedDate(value);
setCurrentMonth(value);
}
}, [value]);
React.useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const year = currentMonth.getFullYear();
const month = currentMonth.getMonth();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDayOfWeek = new Date(year, month, 1).getDay();
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
const handlePrevMonth = () => {
setCurrentMonth(new Date(year, month - 1, 1));
};
const handleNextMonth = () => {
setCurrentMonth(new Date(year, month + 1, 1));
};
const handleSelectDay = (day: number) => {
const newDate = new Date(year, month, day);
setSelectedDate(newDate);
onChange?.(newDate);
setIsOpen(false);
};
const formatDate = (date: Date) => {
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
};
return (
<div ref={containerRef} className="relative w-full flex flex-col gap-1.5 max-w-xs">
{label && <label className="text-xs font-semibold text-foreground/90 select-none">{label}</label>}
<button
type="button"
disabled={disabled}
onClick={() => setIsOpen((prev) => !prev)}
className="h-10 w-full flex items-center justify-between rounded-xl border border-input bg-background px-3 py-2 text-sm shadow-xs outline-none focus:ring-2 focus:ring-ring cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
<span className={cn("truncate", !selectedDate && "text-muted-foreground")}>
{selectedDate ? formatDate(selectedDate) : placeholder}
</span>
<CalendarIcon className="size-4 shrink-0 opacity-50 ml-2" />
</button>
{isOpen && (
<div className="absolute top-full left-0 z-50 mt-1 w-64 rounded-2xl border border-border bg-popover text-popover-foreground shadow-lg p-3 animate-in fade-in-80">
<div className="flex items-center justify-between mb-3">
<button
type="button"
onClick={handlePrevMonth}
className="p-1 hover:bg-accent rounded-lg text-muted-foreground hover:text-foreground cursor-pointer"
>
<ChevronLeft className="size-4" />
</button>
<span className="text-xs font-semibold">
{monthNames[month]} {year}
</span>
<button
type="button"
onClick={handleNextMonth}
className="p-1 hover:bg-accent rounded-lg text-muted-foreground hover:text-foreground cursor-pointer"
>
<ChevronRight className="size-4" />
</button>
</div>
<div className="grid grid-cols-7 gap-1 text-center text-[10px] font-semibold text-muted-foreground mb-1">
<span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span>
</div>
<div className="grid grid-cols-7 gap-1">
{Array.from({ length: firstDayOfWeek }).map((_, i) => (
<div key={`empty-${i}`} />
))}
{Array.from({ length: daysInMonth }).map((_, i) => {
const day = i + 1;
const isSelected =
selectedDate &&
selectedDate.getDate() === day &&
selectedDate.getMonth() === month &&
selectedDate.getFullYear() === year;
return (
<button
key={day}
type="button"
onClick={() => handleSelectDay(day)}
className={cn(
"size-7 rounded-lg text-xs flex items-center justify-center transition-colors cursor-pointer hover:bg-accent hover:text-accent-foreground",
isSelected && "bg-primary text-primary-foreground font-semibold hover:bg-primary/90"
)}
>
{day}
</button>
);
})}
</div>
</div>
)}
</div>
);
}Basic Usage
Standard date picker.
API Reference
Props — DatePicker
Properties to configure the DatePicker component.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | Date | — | Selected JavaScript Date object. |
| placeholder | string | 'Select date...' | Trigger label placeholder. |