Command Palette

Search for a command to run...

Date Range Picker

A single-trigger date-range picker with a preset rail and a calendar.

A single-trigger date-range picker with a preset rail down the left and a calendar on the right. Selecting a preset highlights its range on the calendar; picking days directly clears the preset and becomes a custom range.

Built from design-system primitives: Menu (popover + radio rail), Calendar, Button, and an icon.

Usage

import { useState } from "react";
 
import {
  DateRangePicker,
  DEFAULT_PRESETS,
  type DateRangeSelection,
} from "@smartacteam/ambient-web/date-range-picker";
 
const [value, setValue] = useState<DateRangeSelection>({
  preset: "15d",
  range: DEFAULT_PRESETS.find((preset) => preset.key === "15d")!.getRange(
    new Date(),
  ),
});
 
<DateRangePicker value={value} onChange={setValue} />;

The component is controlled — it never holds its own selection. onChange fires with { preset, range }:

  • Choosing a preset → { preset: "<key>", range: { from, to } }
  • Picking days on the calendar → { preset: null, range: { from, to } }, plus the clicked day as a second argument

A consumer that adjusts the selection uses that second argument to decide which bound to hold — the day the user just clicked keeps its place.

Custom presets

Pass your own presets. Each resolves to a concrete range relative to now, so it stays correct regardless of when the picker opens. Supply translated labels here to localize the rail. Use the now prop to anchor presets to a fixed clock (handy when working against historical data). Build ranges with date-fns helpers.

import { addDays, endOfDay, startOfDay } from "date-fns";
 
const presets: DateRangePreset[] = [
  {
    key: "24h",
    label: "Last 24 hours",
    getRange: (now) => ({ from: addDays(now, -1), to: now }),
  },
  {
    key: "7d",
    label: "Last 7 days",
    getRange: (now) => ({
      from: startOfDay(addDays(now, -6)),
      to: endOfDay(now),
    }),
  },
  {
    key: "30d",
    label: "Last 30 days",
    getRange: (now) => ({
      from: startOfDay(addDays(now, -29)),
      to: endOfDay(now),
    }),
  },
];
 
<DateRangePicker value={value} onChange={setValue} presets={presets} />;

Notice

A consumer that constrains the selection — a maximum span, a floor on how far back data goes — passes notice to explain the constraint in the popup. It renders below the preset rail and is announced when it appears, so a selection the consumer adjusts says so where the user is looking.

The rail runs shorter than the calendar, so the notice occupies spare height: the popup keeps its size whether or not one is showing. Keep the text short — the rail is 10rem wide and the notice wraps within it.

<DateRangePicker
  value={value}
  onChange={setValue}
  notice={
    trimmed ? "Range trimmed to 7 days, the most sensor data covers." : null
  }
/>

Props

PropTypeDefaultDescription
valueDateRangeSelectionrequired{ preset: string | null, range }
onChange(value: DateRangeSelection, clickedDay?: Date) => voidrequiredFires on preset or calendar selection; a calendar pick passes the clicked day
presetsDateRangePreset[]DEFAULT_PRESETSThe left-rail options
nowDatenew Date()Anchor for relative presets
numberOfMonthsnumber1Calendar months shown side by side
align"start" | "center" | "end""start"Popover alignment to the trigger
placeholderstring"Select dates"Trigger label when nothing is selected
noticeReactNodeMessage below the preset rail

Exports

ExportDescription
DateRangePickerThe component
DEFAULT_PRESETSToday / Yesterday / Last 3, 7, 15, 30 days
formatRange({from, to}) => "Jun 2 – Jun 16, 2024"

Build custom preset ranges with date-fns (startOfDay, endOfDay, addDays).