codefast/ui

Command Palette

Search for a command to run...

Form

Date Picker

A date and date-range picker composed from a Popover and the Calendar component.

Examples

Basic

A labelled date field that opens a single-date calendar.

29 lines
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { useState } from "react";

function formatLong(date: Date): string {
  return date.toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric" });
}

export function DatePickerBasic() {
  const [date, setDate] = useState<Date>();

  return (
    <Field className="mx-auto w-44">
      <FieldLabel htmlFor="date-picker-basic">Date</FieldLabel>
      <Popover>
        <PopoverTrigger asChild>
          <Button variant="outline" id="date-picker-basic" className="justify-start font-normal">
            {date ? formatLong(date) : <span>Pick a date</span>}
          </Button>
        </PopoverTrigger>
        <PopoverContent align="start" className="w-auto p-0">
          <Calendar mode="single" selected={date} onSelect={setDate} {...(date ? { defaultMonth: date } : {})} />
        </PopoverContent>
      </Popover>
    </Field>
  );
}

Range Picker

Select a start and end date across two months with mode=range.

64 lines
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";

/** Structural match for @daypicker/react's DateRange (kept local to avoid a transitive import). */
interface DateRange {
  from: Date | undefined;
  to?: Date | undefined;
}

function addDays(date: Date, days: number): Date {
  const next = new Date(date);
  next.setDate(next.getDate() + days);
  return next;
}

function formatShort(date: Date): string {
  return date.toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" });
}

const initialFrom = new Date(new Date().getFullYear(), 0, 20);

export function DatePickerRange() {
  const [date, setDate] = useState<DateRange | undefined>({
    from: initialFrom,
    to: addDays(initialFrom, 20),
  });

  return (
    <Field className="mx-auto w-60">
      <FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>
      <Popover>
        <PopoverTrigger asChild>
          <Button variant="outline" id="date-picker-range" className="justify-start px-2.5 font-normal">
            <CalendarIcon />
            {date?.from ? (
              date.to ? (
                <>
                  {formatShort(date.from)} - {formatShort(date.to)}
                </>
              ) : (
                formatShort(date.from)
              )
            ) : (
              <span>Pick a date</span>
            )}
          </Button>
        </PopoverTrigger>
        <PopoverContent align="start" className="w-auto p-0">
          <Calendar
            mode="range"
            defaultMonth={date?.from ?? initialFrom}
            selected={date}
            onSelect={setDate}
            numberOfMonths={2}
          />
        </PopoverContent>
      </Popover>
    </Field>
  );
}

Date of Birth

A dropdown caption lets users jump to a distant month or year quickly.

35 lines
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { useState } from "react";

export function DatePickerDob() {
  const [open, setOpen] = useState(false);
  const [date, setDate] = useState<Date | undefined>(undefined);

  return (
    <Field className="mx-auto w-44">
      <FieldLabel htmlFor="date-of-birth">Date of birth</FieldLabel>
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button variant="outline" id="date-of-birth" className="justify-start font-normal">
            {date ? date.toLocaleDateString() : "Select date"}
          </Button>
        </PopoverTrigger>
        <PopoverContent align="start" className="w-auto overflow-hidden p-0">
          <Calendar
            mode="single"
            selected={date}
            captionLayout="dropdown"
            {...(date ? { defaultMonth: date } : {})}
            onSelect={(value) => {
              setDate(value);
              setOpen(false);
            }}
          />
        </PopoverContent>
      </Popover>
    </Field>
  );
}

Input

Type a date in the field or pick it from the calendar — the two stay in sync.

81 lines
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";

function formatDate(date: Date | undefined): string {
  if (!date) {
    return "";
  }

  return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric" });
}

function isValidDate(date: Date | undefined): boolean {
  if (!date) {
    return false;
  }

  return !Number.isNaN(date.getTime());
}

const initialDate = new Date("2025-06-01");

export function DatePickerInput() {
  const [open, setOpen] = useState(false);
  const [date, setDate] = useState<Date | undefined>(initialDate);
  const [month, setMonth] = useState<Date>(initialDate);
  const [value, setValue] = useState(formatDate(initialDate));

  return (
    <Field className="mx-auto w-48">
      <FieldLabel htmlFor="date-input">Subscription Date</FieldLabel>
      <InputGroup>
        <InputGroupInput
          id="date-input"
          value={value}
          placeholder="June 01, 2025"
          onChange={(event) => {
            const next = new Date(event.target.value);
            setValue(event.target.value);
            if (isValidDate(next)) {
              setDate(next);
              setMonth(next);
            }
          }}
          onKeyDown={(event) => {
            if (event.key === "ArrowDown") {
              event.preventDefault();
              setOpen(true);
            }
          }}
        />
        <InputGroupAddon align="inline-end">
          <Popover open={open} onOpenChange={setOpen}>
            <PopoverTrigger asChild>
              <InputGroupButton id="date-input-trigger" variant="ghost" size="icon-xs" aria-label="Select date">
                <CalendarIcon />
                <span className="sr-only">Select date</span>
              </InputGroupButton>
            </PopoverTrigger>
            <PopoverContent align="end" alignOffset={-8} sideOffset={10} className="w-auto overflow-hidden p-0">
              <Calendar
                mode="single"
                selected={date}
                month={month}
                onMonthChange={setMonth}
                onSelect={(next) => {
                  setDate(next);
                  setValue(formatDate(next));
                  setOpen(false);
                }}
              />
            </PopoverContent>
          </Popover>
        </InputGroupAddon>
      </InputGroup>
    </Field>
  );
}

Time Picker

Pair a date picker with a native time input.

54 lines
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";

function formatLong(date: Date): string {
  return date.toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric" });
}

export function DatePickerTime() {
  const [open, setOpen] = useState(false);
  const [date, setDate] = useState<Date | undefined>(undefined);

  return (
    <FieldGroup className="mx-auto max-w-xs flex-row">
      <Field>
        <FieldLabel htmlFor="date-picker-time">Date</FieldLabel>
        <Popover open={open} onOpenChange={setOpen}>
          <PopoverTrigger asChild>
            <Button variant="outline" id="date-picker-time" className="w-32 justify-between font-normal">
              {date ? formatLong(date) : "Select date"}
              <ChevronDownIcon />
            </Button>
          </PopoverTrigger>
          <PopoverContent align="start" className="w-auto overflow-hidden p-0">
            <Calendar
              mode="single"
              selected={date}
              captionLayout="dropdown"
              {...(date ? { defaultMonth: date } : {})}
              onSelect={(value) => {
                setDate(value);
                setOpen(false);
              }}
            />
          </PopoverContent>
        </Popover>
      </Field>
      <Field className="w-32">
        <FieldLabel htmlFor="time-picker">Time</FieldLabel>
        <Input
          type="time"
          id="time-picker"
          step="1"
          defaultValue="10:30:00"
          className="appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
        />
      </Field>
    </FieldGroup>
  );
}

Natural Language

Parse phrases like “in 2 days” into a date with chrono-node.

Your post will be published on July 30, 2026.
73 lines
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { parseDate } from "chrono-node";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";

function formatDate(date: Date | undefined): string {
  if (!date) {
    return "";
  }

  return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric" });
}

export function DatePickerNaturalLanguage() {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState("In 2 days");
  const [date, setDate] = useState<Date | undefined>(parseDate(value) ?? undefined);

  return (
    <Field className="mx-auto max-w-xs">
      <FieldLabel htmlFor="date-natural">Schedule Date</FieldLabel>
      <InputGroup>
        <InputGroupInput
          id="date-natural"
          value={value}
          placeholder="Tomorrow or next week"
          onChange={(event) => {
            setValue(event.target.value);
            const next = parseDate(event.target.value);
            if (next) {
              setDate(next);
            }
          }}
          onKeyDown={(event) => {
            if (event.key === "ArrowDown") {
              event.preventDefault();
              setOpen(true);
            }
          }}
        />
        <InputGroupAddon align="inline-end">
          <Popover open={open} onOpenChange={setOpen}>
            <PopoverTrigger asChild>
              <InputGroupButton id="date-natural-trigger" variant="ghost" size="icon-xs" aria-label="Select date">
                <CalendarIcon />
                <span className="sr-only">Select date</span>
              </InputGroupButton>
            </PopoverTrigger>
            <PopoverContent align="end" sideOffset={8} className="w-auto overflow-hidden p-0">
              <Calendar
                mode="single"
                selected={date}
                captionLayout="dropdown"
                {...(date ? { defaultMonth: date } : {})}
                onSelect={(next) => {
                  setDate(next);
                  setValue(formatDate(next));
                  setOpen(false);
                }}
              />
            </PopoverContent>
          </Popover>
        </InputGroupAddon>
      </InputGroup>
      <div className="px-1 text-sm text-muted-foreground">
        Your post will be published on <span className="font-medium">{formatDate(date)}</span>.
      </div>
    </Field>
  );
}

RTL

Right-to-left layout support for languages such as Arabic and Hebrew.

Translations are AI-generated for demonstration and may be imperfect.

58 lines
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { arSA, he } from "@daypicker/react/locale";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";

import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";

const translations: Translations = {
  en: { dir: "ltr", values: { placeholder: "Pick a date" } },
  ar: { dir: "rtl", values: { placeholder: "اختر تاريخًا" } },
  he: { dir: "rtl", values: { placeholder: "בחר תاريخًا" } },
};

const dayPickerLocales = { ar: arSA, he } as const;

/** BCP-47 tag for Intl date formatting, derived from the active language. */
const intlLocales: Record<string, string> = { ar: "ar-SA", he: "he-IL" };

export function DatePickerRtl() {
  const { dir, t, language } = useTranslation(translations, "ar");
  const [date, setDate] = useState<Date>();

  const dayPickerLocale = dir === "rtl" ? dayPickerLocales[language as keyof typeof dayPickerLocales] : undefined;
  const intlLocale = dir === "rtl" ? (intlLocales[language] ?? "en-US") : "en-US";

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          dir={dir}
          data-empty={!date}
          className="w-53 justify-between text-start font-normal data-[empty=true]:text-muted-foreground"
        >
          {date ? (
            date.toLocaleDateString(intlLocale, { day: "numeric", month: "long", year: "numeric" })
          ) : (
            <span>{t.placeholder}</span>
          )}
          <ChevronDownIcon />
        </Button>
      </PopoverTrigger>
      <PopoverContent align="start" dir={dir} className="w-auto p-0">
        <Calendar
          mode="single"
          selected={date}
          onSelect={setDate}
          dir={dir}
          locale={dayPickerLocale}
          {...(date ? { defaultMonth: date } : {})}
        />
      </PopoverContent>
    </Popover>
  );
}

Anatomy

How the parts nest — every slot the component exposes, in composition order.

Popover
├── PopoverTrigger
│ └── Button
└── PopoverContent
└── Calendar

Features

  • Composes Popover + Calendar — inherits their focus-trap and keyboard behaviour for free, nothing reimplemented.
  • The demonstrated recipes cover a single date, a start/end range (mode="range"), a typed date kept in sync with the calendar, and natural-language parsing ("in 2 days") via chrono-node.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Compose the picker from Popover + Calendar so it inherits their focus and keyboard behaviour.
  • Mirror the calendar selection back into any text input so both stay in sync.

Don’t

  • Don’t block typing — let users enter a date directly as well as pick one.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.