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.

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.

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-40" type="time" />
    </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