CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-stately--datepicker

React state management hooks for date picker components with internationalization and accessibility support.

Pending
Overview
Eval results
Files

date-range-picker-state.mddocs/

Date Range Picker State

State management for date range picker components that allow users to select a start and end date. Combines two date fields with a range calendar popover to enable comprehensive date range selection with validation and formatting.

Capabilities

useDateRangePickerState Hook

Creates a state object for managing date range picker component state including start/end date values, overlay visibility, and range validation.

/**
 * Provides state management for a date range picker component.
 * A date range picker combines two DateFields and a RangeCalendar popover to allow
 * users to enter or select a date and time range.
 * @param props - Configuration options for the date range picker state
 * @returns DateRangePickerState object with range management and overlay controls
 */
function useDateRangePickerState<T extends DateValue = DateValue>(
  props: DateRangePickerStateOptions<T>
): DateRangePickerState;

interface DateRangePickerStateOptions<T extends DateValue = DateValue> extends DateRangePickerProps<T> {
  /**
   * Determines whether the date picker popover should close automatically when a date range is selected.
   * @default true
   */
  shouldCloseOnSelect?: boolean | (() => boolean);
}

interface DateRangePickerState extends OverlayTriggerState, FormValidationState {
  /** The currently selected date range. */
  value: RangeValue<DateValue | null>;
  /** The default selected date range. */
  defaultValue: DateRange | null;
  /** Sets the selected date range. */
  setValue(value: DateRange | null): void;
  /** The date portion of the selected range. This may be set prior to `value` if the user has selected a date range but has not yet selected a time range. */
  dateRange: RangeValue<DateValue | null> | null;
  /** Sets the date portion of the selected range. */
  setDateRange(value: DateRange): void;
  /** The time portion of the selected range. This may be set prior to `value` if the user has selected a time range but has not yet selected a date range. */
  timeRange: RangeValue<TimeValue | null> | null;
  /** Sets the time portion of the selected range. */
  setTimeRange(value: RangeValue<TimeValue | null>): void;
  /** Sets the date portion of either the start or end of the selected range. */
  setDate(part: 'start' | 'end', value: DateValue | null): void;
  /** Sets the time portion of either the start or end of the selected range. */
  setTime(part: 'start' | 'end', value: TimeValue | null): void;
  /** Sets the date and time of either the start or end of the selected range. */
  setDateTime(part: 'start' | 'end', value: DateValue | null): void;
  /** The granularity for the field, based on the `granularity` prop and current value. */
  granularity: Granularity;
  /** Whether the date range picker supports selecting times, according to the `granularity` prop and current value. */
  hasTime: boolean;
  /** Whether the calendar popover is currently open. */
  isOpen: boolean;
  /** Sets whether the calendar popover is open. */
  setOpen(isOpen: boolean): void;
  /** The current validation state of the date range picker, based on the `validationState`, `minValue`, and `maxValue` props. @deprecated Use `isInvalid` instead. */
  validationState: ValidationState | null;
  /** Whether the date range picker is invalid, based on the `isInvalid`, `minValue`, and `maxValue` props. */
  isInvalid: boolean;
  /** Formats the selected range using the given options. */
  formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;
  /** Gets a formatter based on state's props. */
  getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter;
}

Usage Examples:

import { useDateRangePickerState } from "@react-stately/datepicker";
import { CalendarDate, CalendarDateTime } from "@internationalized/date";

// Basic date range picker
function BasicDateRangePicker() {
  const state = useDateRangePickerState({
    defaultValue: {
      start: new CalendarDate(2023, 6, 1),
      end: new CalendarDate(2023, 6, 15)
    },
    onChange: (range) => {
      if (range) {
        console.log("Range:", range.start.toString(), "to", range.end.toString());
      }
    }
  });

  const formatValue = state.formatValue('en-US', {});

  return (
    <div>
      <div>
        <input 
          value={formatValue?.start || "Start date"} 
          readOnly 
          onClick={() => state.setOpen(true)}
        />
        <span> to </span>
        <input 
          value={formatValue?.end || "End date"} 
          readOnly 
          onClick={() => state.setOpen(true)}
        />
      </div>
      {state.isOpen && <div>Range calendar popover</div>}
    </div>
  );
}

// Date and time range picker
function DateTimeRangePicker() {
  const state = useDateRangePickerState({
    granularity: 'minute',
    shouldCloseOnSelect: false, // Keep open for time selection
    onChange: (range) => {
      if (range?.start && range?.end) {
        console.log("Start:", range.start.toDate('UTC'));
        console.log("End:", range.end.toDate('UTC'));
      }
    }
  });

  return (
    <div>
      <button onClick={() => state.setOpen(!state.isOpen)}>
        {state.value?.start && state.value?.end
          ? `${state.value.start.toString()} - ${state.value.end.toString()}`
          : "Select date range"
        }
      </button>
      
      {state.hasTime && <span>Includes time selection</span>}
      
      {state.isOpen && (
        <div>
          <div>Range calendar with time selectors</div>
          <button onClick={() => state.setOpen(false)}>Close</button>
        </div>
      )}
    </div>
  );
}

// Controlled range picker with individual date/time setting
function ControlledRangePicker({ value, onChange }) {
  const state = useDateRangePickerState({
    value,
    onChange,
    granularity: 'hour',
    minValue: new CalendarDateTime(2023, 1, 1, 0),
    maxValue: new CalendarDateTime(2024, 12, 31, 23),
    startName: 'startDate',
    endName: 'endDate'
  });

  const handleStartDateChange = (date: CalendarDate) => {
    state.setDate('start', date);
  };

  const handleEndTimeChange = (time: Time) => {
    state.setTime('end', time);
  };

  return (
    <div>
      <div style={{ border: state.isInvalid ? '2px solid red' : '1px solid #ccc' }}>
        Range: {state.value?.start?.toString()} to {state.value?.end?.toString()}
      </div>
      
      <div>
        <button onClick={() => handleStartDateChange(new CalendarDate(2023, 6, 1))}>
          Set Start Date
        </button>
        <button onClick={() => handleEndTimeChange(new Time(18, 0))}>
          Set End Time to 6 PM
        </button>
      </div>
      
      {state.dateRange && !state.value?.start && (
        <div>Date range selected, awaiting time selection</div>
      )}
    </div>
  );
}

Range Value Management

The date range picker state manages separate date and time ranges that are combined into the final value.

/**
 * Sets the date portion of the selected range
 * @param value - The date range to set
 */
setDateRange(value: DateRange): void;

/**
 * Sets the time portion of the selected range
 * @param value - The time range to set
 */
setTimeRange(value: RangeValue<TimeValue | null>): void;

/**
 * Sets the date portion of either start or end
 * @param part - Whether to set 'start' or 'end' date
 * @param value - The date value to set
 */
setDate(part: 'start' | 'end', value: DateValue | null): void;

/**
 * Sets the time portion of either start or end
 * @param part - Whether to set 'start' or 'end' time
 * @param value - The time value to set
 */
setTime(part: 'start' | 'end', value: TimeValue | null): void;

/**
 * Sets both date and time for either start or end
 * @param part - Whether to set 'start' or 'end' datetime
 * @param value - The datetime value to set
 */
setDateTime(part: 'start' | 'end', value: DateValue | null): void;

Range Formatting

Provides intelligent formatting that can optimize shared date parts between start and end dates.

/**
 * Formats the selected range using the given options
 * Automatically optimizes formatting when start and end share common parts (e.g., same month)
 * @param locale - The locale to format in (e.g. 'en-US', 'fr-FR')
 * @param fieldOptions - Formatting options for different date/time parts
 * @returns Object with formatted start and end strings, or null if no complete range
 */
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;

Example formatting behavior:

// Same month: "June 1 - 15, 2023"
// Different months: "June 1 - July 15, 2023"  
// Different years: "Dec 31, 2023 - Jan 1, 2024"

Range Types

interface RangeValue<T> {
  start: T;
  end: T;
}

type DateRange = RangeValue<DateValue>;
type TimeRange = RangeValue<TimeValue>;

Validation

Range validation includes all standard date validation plus range-specific validation.

interface DateRangePickerState {
  /** Whether the date range picker is invalid, based on validation props and range logic */
  isInvalid: boolean;
  /** The current validation state @deprecated Use `isInvalid` instead */
  validationState: ValidationState | null;
}

Range-specific validation errors:

  • Range reversed: End date is before start date
  • Start date invalid: Start date violates min/max/unavailable constraints
  • End date invalid: End date violates min/max/unavailable constraints
  • Range too long: If custom validation restricts range duration

Form Integration

Supports HTML forms with separate names for start and end dates.

interface DateRangePickerProps<T> {
  /** The name of the start date input element for HTML forms */
  startName?: string;
  /** The name of the end date input element for HTML forms */
  endName?: string;
  /** Whether user input is required before form submission */
  isRequired?: boolean;
}

Example with form submission:

const state = useDateRangePickerState({
  startName: 'trip_start',
  endName: 'trip_end',
  isRequired: true,
  onChange: (range) => {
    // Form will include trip_start and trip_end fields
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-react-stately--datepicker

docs

date-field-state.md

date-picker-state.md

date-range-picker-state.md

index.md

time-field-state.md

tile.json