Componentpickers2.15 KB gzipZero dependencies

Date Picker

Date picker input with popover calendar and quick date selection.

Preview

Live Preview
Click to open admission calendar popover

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add date-picker

Or install the full npm package:

npm install bigbullui
import { DatePicker } from "@/components/ui/date-picker";

<DatePicker defaultValue={new Date()} onValueChange={(d) => console.log(d)} />

Usage guidance

Use Date Picker when a ticket stub surface needs Date picker input with popover calendar and quick date selection. Keep props minimal and prefer composition with sibling stubs.

Avoid Date Picker for long form flows or dense data grids where a dedicated layout block fits better.

Source code

date-picker.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface DatePickerProps {
  value?: Date;
  defaultValue?: Date;
  onValueChange?: (date: Date) => void;
  placeholder?: string;
  className?: string;
}

const MONTHS = [
  "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
  "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"
];
const DAYS = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];

export function DatePicker({
  value: controlledValue,
  defaultValue,
  onValueChange,
  placeholder = "SELECT ADMISSION DATE",
  className,
}: DatePickerProps) {
  const [internalDate, setInternalDate] = React.useState<Date | undefined>(defaultValue);
  const [isOpen, setIsOpen] = React.useState(false);
  const containerRef = React.useRef<HTMLDivElement>(null);

  const isControlled = controlledValue !== undefined;
  const selectedDate = isControlled ? controlledValue : internalDate;

  const [viewDate, setViewDate] = React.useState(selectedDate || new Date());
  const year = viewDate.getFullYear();
  const month = viewDate.getMonth();

  React.useEffect(() => {
    if (!isOpen) return;
    const handleClickOutside = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setIsOpen(false);
      }
    };
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape") setIsOpen(false);
    };

    document.addEventListener("mousedown", handleClickOutside);
    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [isOpen]);

  const handleSelectDay = (day: number) => {
    const newDate = new Date(year, month, day);
    if (!isControlled) {
      setInternalDate(newDate);
    }
    onValueChange?.(newDate);
    setIsOpen(false);
  };

  const formatDate = (date: Date) => {
    const d = date.getDate().toString().padStart(2, "0");
    const m = MONTHS[date.getMonth()].slice(0, 3);
    const y = date.getFullYear();
    return `${d} ${m} ${y}`;
  };

  const firstDayOfWeek = new Date(year, month, 1).getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();

  return (
    <div ref={containerRef} className={cn("relative inline-block font-mono select-none", className)}>
      <button
        type="button"
        onClick={() => setIsOpen((prev) => !prev)}
        className={cn(
          "flex min-w-[220px] cursor-pointer items-center justify-between rounded-lg border-2 border-foreground bg-card px-3 py-2 text-xs transition-all shadow-xs",
          "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
          isOpen ? "border-accent ring-1 ring-accent" : "hover:border-foreground/70"
        )}
      >
        <div className="flex items-center gap-2">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4"><rect x="3.5" y="5" width="17" height="15.5" rx="1.5" /><path d="M3.5 9.8h17M8 3v3.6M16 3v3.6M7.5 13.5h2M11 13.5h2M14.5 13.5h2M7.5 16.8h2M11 16.8h2" /></svg>
          <span className={cn("font-bold uppercase", !selectedDate && "text-muted-foreground font-normal")}>
            {selectedDate ? formatDate(selectedDate) : placeholder}
          </span>
        </div>
        <span className="text-[10px] text-muted-foreground transition-transform duration-150">
          {isOpen ? "▲" : "▼"}
        </span>
      </button>

      {isOpen && (
        <div className="absolute left-0 top-full z-50 mt-2 inline-flex flex-col rounded-xl border-2 border-foreground bg-card p-4 shadow-2xl outline-1 outline-dashed outline-offset-[-6px] animate-[scale-in_0.15s_ease-out_both] w-72">
          {/* Header */}
          <div className="flex items-center justify-between border-b-2 border-dashed border-border pb-3">
            <button
              type="button"
              onClick={() => setViewDate(new Date(year, month - 1, 1))}
              className="size-7 rounded-sm border border-border bg-secondary flex items-center justify-center font-mono text-xs hover:bg-foreground hover:text-background cursor-pointer"
            >
              ←
            </button>
            <span className="font-mono text-xs font-bold uppercase tracking-wider text-foreground">
              {MONTHS[month]} {year}
            </span>
            <button
              type="button"
              onClick={() => setViewDate(new Date(year, month + 1, 1))}
              className="size-7 rounded-sm border border-border bg-secondary flex items-center justify-center font-mono text-xs hover:bg-foreground hover:text-background cursor-pointer"
            >
              →
            </button>
          </div>

          {/* Days labels */}
          <div className="mt-3 grid grid-cols-7 gap-1 text-center font-mono text-[10px] uppercase text-muted-foreground font-semibold">
            {DAYS.map((d) => (
              <div key={d} className="py-1">
                {d}
              </div>
            ))}
          </div>

          {/* Days Grid */}
          <div className="mt-1 grid grid-cols-7 gap-1 text-center font-mono text-xs">
            {Array.from({ length: firstDayOfWeek }).map((_, i) => (
              <div key={`blank-${i}`} className="size-8" />
            ))}
            {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-${day}`}
                  type="button"
                  onClick={() => handleSelectDay(day)}
                  className={cn(
                    "size-8 rounded-full flex items-center justify-center font-mono text-xs transition-all cursor-pointer",
                    isSelected
                      ? "bg-accent font-bold text-accent-foreground shadow-xs scale-105"
                      : "hover:bg-secondary text-foreground"
                  )}
                >
                  {day}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

Props

PropTypeDescription
value?Date
defaultValue?Date
onValueChange?(date: Date) => void
placeholder?string
className?string