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-field-state.mddocs/

Date Field State

State management for date field components with individually editable segments. Each part of a date value (year, month, day, hour, minute, second) is displayed in a separate editable segment, providing precise keyboard-based date and time input.

Capabilities

useDateFieldState Hook

Creates a state object for managing date field component state with segment-based editing, validation, and formatting.

/**
 * Provides state management for a date field component.
 * A date field allows users to enter and edit date and time values using a keyboard.
 * Each part of a date value is displayed in an individually editable segment.
 * @param props - Configuration options including locale and calendar system
 * @returns DateFieldState object with segment management and validation
 */
function useDateFieldState<T extends DateValue = DateValue>(
  props: DateFieldStateOptions<T>
): DateFieldState;

interface DateFieldStateOptions<T extends DateValue = DateValue> extends DatePickerProps<T> {
  /**
   * The maximum unit to display in the date field.
   * @default 'year'
   */
  maxGranularity?: 'year' | 'month' | Granularity;
  /** The locale to display and edit the value according to. */
  locale: string;
  /**
   * A function that creates a Calendar object for a given calendar identifier.
   * Such a function may be imported from the @internationalized/date package.
   */
  createCalendar: (name: CalendarIdentifier) => Calendar;
}

interface DateFieldState extends FormValidationState {
  /** The current field value. */
  value: DateValue | null;
  /** The default field value. */
  defaultValue: DateValue | null;
  /** The current value, converted to a native JavaScript Date object. */
  dateValue: Date;
  /** The calendar system currently in use. */
  calendar: Calendar;
  /** Sets the field's value. */
  setValue(value: DateValue | null): void;
  /** A list of segments for the current value. */
  segments: DateSegment[];
  /** A date formatter configured for the current locale and format. */
  dateFormatter: DateFormatter;
  /** The current validation state of the date field, based on the `validationState`, `minValue`, and `maxValue` props. @deprecated Use `isInvalid` instead. */
  validationState: ValidationState | null;
  /** Whether the date field is invalid, based on the `isInvalid`, `minValue`, and `maxValue` props. */
  isInvalid: boolean;
  /** The granularity for the field, based on the `granularity` prop and current value. */
  granularity: Granularity;
  /** The maximum date or time unit that is displayed in the field. */
  maxGranularity: 'year' | 'month' | Granularity;
  /** Whether the field is disabled. */
  isDisabled: boolean;
  /** Whether the field is read only. */
  isReadOnly: boolean;
  /** Whether the field is required. */
  isRequired: boolean;
  /** Increments the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
  increment(type: SegmentType): void;
  /** Decrements the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
  decrement(type: SegmentType): void;
  /** Increments the given segment by a larger amount, rounding it to the nearest increment. The amount to increment by depends on the field, for example 15 minutes, 7 days, and 5 years. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
  incrementPage(type: SegmentType): void;
  /** Decrements the given segment by a larger amount, rounding it to the nearest increment. The amount to decrement by depends on the field, for example 15 minutes, 7 days, and 5 years. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
  decrementPage(type: SegmentType): void;
  /** Sets the value of the given segment. */
  setSegment(type: 'era', value: string): void;
  setSegment(type: SegmentType, value: number): void;
  /** Updates the remaining unfilled segments with the placeholder value. */
  confirmPlaceholder(): void;
  /** Clears the value of the given segment, reverting it to the placeholder. */
  clearSegment(type: SegmentType): void;
  /** Formats the current date value using the given options. */
  formatValue(fieldOptions: FieldOptions): string;
  /** Gets a formatter based on state's props. */
  getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter;
}

Usage Examples:

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

// Basic date field
function BasicDateField() {
  const state = useDateFieldState({
    locale: 'en-US',
    createCalendar,
    defaultValue: new CalendarDate(2023, 6, 15),
    onChange: (value) => console.log("Date changed:", value?.toString())
  });

  return (
    <div>
      {state.segments.map((segment, index) => (
        <span
          key={index}
          style={{
            padding: '2px',
            backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
            border: segment.isEditable ? '1px solid #ccc' : 'none'
          }}
        >
          {segment.text}
        </span>
      ))}
      <div>Current value: {state.value?.toString()}</div>
    </div>
  );
}

// Time field with segment manipulation
function TimeField() {
  const state = useDateFieldState({
    locale: 'en-US',
    createCalendar,
    granularity: 'second',
    maxGranularity: 'hour',
    onChange: (value) => console.log("Time:", value)
  });

  const handleSegmentIncrement = (segment: DateSegment) => {
    if (segment.isEditable) {
      state.increment(segment.type);
    }
  };

  return (
    <div>
      {state.segments.map((segment, index) => (
        <button
          key={index}
          disabled={!segment.isEditable}
          onClick={() => handleSegmentIncrement(segment)}
          style={{
            margin: '1px',
            padding: '4px',
            backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white'
          }}
        >
          {segment.text}
        </button>
      ))}
      <div>
        <button onClick={() => state.confirmPlaceholder()}>
          Confirm Placeholder
        </button>
      </div>
    </div>
  );
}

// Date field with validation and custom formatting
function ValidatedDateField() {
  const state = useDateFieldState({
    locale: 'fr-FR',
    createCalendar,
    minValue: new CalendarDate(2020, 1, 1),
    maxValue: new CalendarDate(2030, 12, 31),
    shouldForceLeadingZeros: true,
    isRequired: true,
    onChange: (value) => {
      if (value) {
        console.log("Valid date:", state.formatValue({ 
          year: 'numeric', 
          month: 'long', 
          day: 'numeric' 
        }));
      }
    }
  });

  return (
    <div>
      <div style={{ border: state.isInvalid ? '2px solid red' : '1px solid #ccc' }}>
        {state.segments.map((segment, index) => (
          <input
            key={index}
            value={segment.text}
            placeholder={segment.placeholder}
            readOnly={!segment.isEditable}
            onChange={(e) => {
              if (segment.type === 'era') {
                state.setSegment(segment.type, e.target.value);
              } else {
                const num = parseInt(e.target.value);
                if (!isNaN(num)) {
                  state.setSegment(segment.type, num);
                }
              }
            }}
            style={{ 
              width: `${segment.text.length + 2}ch`,
              border: 'none',
              textAlign: 'center'
            }}
          />
        ))}
      </div>
      {state.isInvalid && <div style={{ color: 'red' }}>Invalid date range</div>}
    </div>
  );
}

Date Segment Interface

Each segment represents an individual editable part of the date/time value.

interface DateSegment {
  /** The type of segment. */
  type: SegmentType;
  /** The formatted text for the segment. */
  text: string;
  /** The numeric value for the segment, if applicable. */
  value?: number;
  /** The minimum numeric value for the segment, if applicable. */
  minValue?: number;
  /** The maximum numeric value for the segment, if applicable. */
  maxValue?: number;
  /** Whether the value is a placeholder. */
  isPlaceholder: boolean;
  /** A placeholder string for the segment. */
  placeholder: string;
  /** Whether the segment is editable. */
  isEditable: boolean;
}

type SegmentType = 'era' | 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal' | 'timeZoneName';

Segment Manipulation Methods

Methods for incrementing, decrementing, and setting individual segments.

/**
 * Increments the given segment by 1. Upon reaching the maximum value, wraps to minimum.
 * @param type - The segment type to increment
 */
increment(type: SegmentType): void;

/**
 * Decrements the given segment by 1. Upon reaching the minimum value, wraps to maximum.
 * @param type - The segment type to decrement  
 */
decrement(type: SegmentType): void;

/**
 * Increments the given segment by a larger amount (page step).
 * Page steps: year=5, month=2, day=7, hour=2, minute=15, second=15
 * @param type - The segment type to increment
 */
incrementPage(type: SegmentType): void;

/**
 * Decrements the given segment by a larger amount (page step).
 * @param type - The segment type to decrement
 */
decrementPage(type: SegmentType): void;

/**
 * Sets the value of a specific segment
 * @param type - The segment type to set
 * @param value - The value to set (string for era, number for others)
 */
setSegment(type: 'era', value: string): void;
setSegment(type: SegmentType, value: number): void;

/**
 * Clears the value of the given segment, reverting it to placeholder
 * @param type - The segment type to clear
 */
clearSegment(type: SegmentType): void;

/**
 * Updates remaining unfilled segments with the placeholder value
 * Useful for completing partial date entry
 */
confirmPlaceholder(): void;

Calendar Integration

The date field integrates with different calendar systems through the @internationalized/date library.

interface DateFieldStateOptions<T> {
  /** The locale to display and edit the value according to. */
  locale: string;
  /**
   * A function that creates a Calendar object for a given calendar identifier.
   * Such a function may be imported from the @internationalized/date package.
   */
  createCalendar: (name: CalendarIdentifier) => Calendar;
}

Example calendar setup:

import { createCalendar } from "@internationalized/date";

const state = useDateFieldState({
  locale: 'ar-SA', // Arabic Saudi Arabia
  createCalendar, // Supports Gregory, Islamic, Buddhist, etc.
  // ... other props
});

Granularity Control

Control which date/time segments are displayed and editable.

interface DateFieldStateOptions<T> {
  /**
   * The maximum unit to display in the date field.
   * @default 'year'
   */
  maxGranularity?: 'year' | 'month' | Granularity;
  /** Determines the smallest unit that is displayed in the date picker. */
  granularity?: Granularity;
}

Examples:

  • granularity: 'day' - Shows year, month, day
  • granularity: 'minute', maxGranularity: 'day' - Shows day, hour, minute
  • granularity: 'second' - Shows all segments including seconds

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