Agents (llms.txt)
Octocat

Date Picker

A date picker component built with the Calendar and Menu components.

import { format } from "date-fns";
import { useState } from "react";

import { DatePicker } from "@/components/date-picker";

export default function DatePickerPreview() {
  const [selectedDate, setSelectedDate] = useState<Date | null>(null);

  return (
    <DatePicker placement="bottom-start">
      <DatePicker.Trigger className="w-60" placeholder="Select date">
        {selectedDate ? format(selectedDate, "PPP") : undefined}
      </DatePicker.Trigger>
      <DatePicker.Panel
        className="w-72"
        value={selectedDate}
        onDateChange={(date: Date) => {
          setSelectedDate(date);
        }}
      />
    </DatePicker>
  );
}

Dependencies

Source Code

import { CalendarIcon, CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr";
import type { VariantProps } from "cva";

import { Calendar } from "@/components/calendar";
import { inputStyle } from "@/components/input";
import { Menu, useMenuPopoverContext } from "@/components/menu";
import { cn } from "@/lib/utils/classnames";

const DatePicker = ({ children, ...props }: React.ComponentProps<typeof Menu>) => {
  return <Menu {...props}>{children}</Menu>;
};

interface DatePickerTriggerProps extends React.ComponentProps<typeof Menu.Trigger> {
  className?: string;
  children: React.ReactNode;
  variant?: VariantProps<typeof inputStyle>["variant"];
  placeholder?: string;
}

const DatePickerTrigger = ({
  children,
  className,
  variant,
  placeholder,
  ...props
}: DatePickerTriggerProps) => {
  return (
    <Menu.Trigger asChild {...props}>
      <button
        type="button"
        className={cn(
          inputStyle({ variant }),
          "flex items-center gap-1.5 enabled:cursor-pointer",
          "relative w-full pr-10 pl-4",
          className,
        )}
      >
        <CalendarIcon className="shrink-0 text-foreground-secondary" />
        {children ?? <span className="text-foreground-secondary">{placeholder}</span>}
        <CaretUpDownIcon
          weight="bold"
          className="absolute top-1/2 right-3 -translate-y-1/2 text-base text-foreground/80"
        />
      </button>
    </Menu.Trigger>
  );
};

interface DatePickerContentCommonProps
  extends Omit<React.ComponentPropsWithRef<typeof Calendar>, "mode" | "value" | "onDateChange"> {
  className?: string;
  children?: React.ReactNode;
}

interface DatePickerContentSingleProps extends DatePickerContentCommonProps {
  mode?: "single";
  value: Date | null;
  onDateChange: (date: Date) => void;
}

interface DatePickerContentRangeProps extends DatePickerContentCommonProps {
  mode: "range";
  value: [Date, Date] | null;
  onDateChange: (dates: [Date, Date]) => void;
}

type DatePickerContentProps = DatePickerContentSingleProps | DatePickerContentRangeProps;

const DatePickerPanel = ({
  className,
  children,
  mode,
  value,
  onDateChange,
  ...props
}: DatePickerContentProps) => {
  const { setOpen } = useMenuPopoverContext();

  return (
    <Menu.Items className={cn(className)}>
      <Calendar
        {...props}
        mode={mode}
        // biome-ignore lint/suspicious/noExplicitAny: expected
        value={value as any}
        onDateChange={(date: Date | [Date, Date]) => {
          setOpen(false);
          // biome-ignore lint/suspicious/noExplicitAny: expected
          onDateChange(date as any);
        }}
      />
      {children}
    </Menu.Items>
  );
};

const CompoundDatePicker = Object.assign(DatePicker, {
  Trigger: DatePickerTrigger,
  Panel: DatePickerPanel,
});

export { CompoundDatePicker as DatePicker };

Features

  • Flexible Input: Supports different input variants and placeholders
  • Shortcuts: Predefined date shortcuts for quick selection
  • Smart Positioning: Automatically adjusts panel position
  • Keyboard Navigation: Full keyboard support inherited from Calendar

Anatomy

          <DatePicker>
  <DatePicker.Trigger />
  <DatePicker.Panel />
</DatePicker>
        

API Reference

DatePicker

Extends the Menu component.

DatePicker.Trigger

Extends the Menu.Trigger component.

PropDefaultTypeDescription
placeholder-stringThe placeholder text to display when the date picker is empty.
variant-InputProps["variant"]Check [Input](/ui/input) for more information.

DatePicker.Panel

Extends the Calendar component.

Examples

Simple Date Picker

Basic usage for selecting a date.

import { format } from "date-fns";
import { useState } from "react";

import { DatePicker } from "@/components/date-picker";

export default function DatePickerPreview() {
  const [selectedDate, setSelectedDate] = useState<Date | null>(null);

  return (
    <DatePicker placement="bottom-start">
      <DatePicker.Trigger className="w-60" placeholder="Select date">
        {selectedDate ? format(selectedDate, "PPP") : undefined}
      </DatePicker.Trigger>
      <DatePicker.Panel
        className="w-72"
        value={selectedDate}
        onDateChange={(date: Date) => {
          setSelectedDate(date);
        }}
      />
    </DatePicker>
  );
}

Date Picker with Time

Adding time selection capability to the date picker.

import { format } from "date-fns";
import { useState } from "react";

import { DatePicker } from "@/components/date-picker";
import { Input } from "@/components/input";

export default function DatePickerDateTimePreview() {
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());

  return (
    <div className="flex items-center gap-2">
      <DatePicker placement="bottom-start">
        <DatePicker.Trigger className="w-60" placeholder="Select date">
          {format(selectedDate, "PPP")}
        </DatePicker.Trigger>
        <DatePicker.Panel
          className="w-72"
          value={selectedDate || new Date()}
          onDateChange={(date: Date) => {
            setSelectedDate(date);
          }}
        />
      </DatePicker>
      <Input className="w-28" type="time" step="1" />
    </div>
  );
}

Shortcuts

Using predefined shortcuts for quick date selection.

import { format } from "date-fns";
import { useState } from "react";

import { DatePicker } from "@/components/date-picker";
import { Menu } from "@/components/menu";

export default function DatePickerShortcutsPreview() {
  const [dateRange, setDateRange] = useState<[Date, Date] | null>(null);

  return (
    <DatePicker placement="bottom-start">
      <DatePicker.Trigger className="w-80" placeholder="Select date range">
        {dateRange
          ? `${format(dateRange[0], "MM/dd/yyyy")} - ${format(dateRange[1], "MM/dd/yyyy")}`
          : undefined}
      </DatePicker.Trigger>
      <DatePicker.Panel
        className="w-80"
        mode="range"
        value={dateRange}
        onDateChange={(dates: [Date, Date]) => {
          setDateRange(dates);
        }}
      >
        <Menu.Divider />
        <Menu.Item
          onSelect={() => {
            setDateRange([new Date(), new Date()]);
          }}
        >
          Today
        </Menu.Item>
        <Menu.Item
          onSelect={() => {
            setDateRange([
              new Date(new Date().setDate(new Date().getDate() - 1)),
              new Date(new Date().setDate(new Date().getDate() - 1)),
            ]);
          }}
        >
          Yesterday
        </Menu.Item>
        <Menu.Item
          onSelect={() => {
            setDateRange([new Date(new Date().setDate(new Date().getDate() - 7)), new Date()]);
          }}
        >
          Last 7 days
        </Menu.Item>
        <Menu.Divider />
        <Menu.Item
          variant="destructive"
          onSelect={() => {
            setDateRange(null);
          }}
        >
          Clear
        </Menu.Item>
      </DatePicker.Panel>
    </DatePicker>
  );
}

Best Practices

  1. Input Format:

    • Use clear date format patterns
    • Consider adding format hints in placeholder
    • Handle invalid date inputs gracefully
  2. Shortcuts:

    • Keep shortcuts relevant to use case
    • Use clear, descriptive labels
    • Consider common date ranges
  3. Mobile Considerations:

    • Test touch interactions
    • Consider using native pickers on mobile
    • Ensure sufficient touch targets

Previous

Color Picker

Next

Dialog