Octocat

Date Picker

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

'use client';

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

import {
  DatePicker,
  DatePickerPanel,
  DatePickerTrigger,
} from '@/components/date-picker';

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

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

Dependencies

Source Code

'use client';

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

import { Calendar } from '@/components/calendar';
import {
  Dropdown,
  DropdownItems,
  DropdownTrigger,
  useDropdownContext,
} from '@/components/dropdown';
import { inputStyle } from '@/components/input';
import { cn } from '@/lib/utils/classnames';

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

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

const DatePickerTrigger = ({
  children,
  className,
  variant,
  placeholder,
  ...props
}: DatePickerTriggerProps) => {
  return (
    <DropdownTrigger 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>
    </DropdownTrigger>
  );
};

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 } = useDropdownContext();

  return (
    <DropdownItems 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}
    </DropdownItems>
  );
};

export { DatePicker, DatePickerPanel, DatePickerTrigger };

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>
  <DatePickerTrigger />
  <DatePickerPanel />
</DatePicker>
        

API Reference

DatePicker

Extends the Dropdown component.

DatePickerTrigger

Extends the DropdownTrigger component.

Prop Default Type Description
placeholder - string The placeholder text to display when the date picker is empty.
variant - InputProps["variant"] Check [Input](/ui/input) for more information.

DatePickerPanel

Extends the Calendar component.

Examples

Simple Date Picker

Basic usage for selecting a date.

'use client';

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

import {
  DatePicker,
  DatePickerPanel,
  DatePickerTrigger,
} from '@/components/date-picker';

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

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

Date Picker with Time

Adding time selection capability to the date picker.

'use client';

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

import {
  DatePicker,
  DatePickerPanel,
  DatePickerTrigger,
} 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">
        <DatePickerTrigger className="w-60" placeholder="Select date">
          {format(selectedDate, 'PPP')}
        </DatePickerTrigger>
        <DatePickerPanel
          className="w-72"
          value={selectedDate || new Date()}
          onDateChange={(date: Date) => {
            setSelectedDate(date);
          }}
        />
      </DatePicker>
      <Input className="w-40" type="time" />
    </div>
  );
}

Shortcuts

Using predefined shortcuts for quick date selection.

'use client';

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

import {
  DatePicker,
  DatePickerPanel,
  DatePickerTrigger,
} from '@/components/date-picker';
import {
  DropdownDivider,
  DropdownItem,
} from '@/components/dropdown';

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

  return (
    <DatePicker placement="bottom-start">
      <DatePickerTrigger className="w-80" placeholder="Select date range">
        {dateRange
          ? `${format(dateRange[0], 'MM/dd/yyyy')} - ${format(dateRange[1], 'MM/dd/yyyy')}`
          : undefined}
      </DatePickerTrigger>
      <DatePickerPanel
        className="w-80"
        mode="range"
        value={dateRange}
        onDateChange={(dates: [Date, Date]) => {
          setDateRange(dates);
        }}
      >
        <DropdownDivider />
        <DropdownItem
          onSelect={() => {
            setDateRange([new Date(), new Date()]);
          }}
        >
          Today
        </DropdownItem>
        <DropdownItem
          onSelect={() => {
            setDateRange([
              new Date(new Date().setDate(new Date().getDate() - 1)),
              new Date(new Date().setDate(new Date().getDate() - 1)),
            ]);
          }}
        >
          Yesterday
        </DropdownItem>
        <DropdownItem
          onSelect={() => {
            setDateRange([
              new Date(new Date().setDate(new Date().getDate() - 7)),
              new Date(),
            ]);
          }}
        >
          Last 7 days
        </DropdownItem>
        <DropdownDivider />
        <DropdownItem
          className="text-red-500"
          onSelect={() => {
            setDateRange(null);
          }}
        >
          Clear
        </DropdownItem>
      </DatePickerPanel>
    </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