CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-dates

A responsive and accessible date range picker component built with React

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

calendar-controllers.mddocs/

Calendar Controllers

Headless calendar components that provide date selection functionality without input fields. These are ideal for custom implementations, embedded calendars, and advanced use cases where you need full control over the user interface.

Capabilities

DayPickerRangeController

Calendar-only component for date range selection without input fields. Perfect for building custom date range interfaces or embedded calendar displays.

/**
 * Headless date range calendar controller without input fields
 * @param props - DayPickerRangeController configuration
 * @returns Calendar component for date range selection
 */
function DayPickerRangeController(props: DayPickerRangeControllerProps): ReactElement;

interface DayPickerRangeControllerProps {
  // Date state
  startDate?: moment.Moment | null;
  endDate?: moment.Moment | null;
  focusedInput?: 'startDate' | 'endDate' | null;

  // Required callbacks
  onDatesChange: ({ startDate, endDate }: {
    startDate: moment.Moment | null;
    endDate: moment.Moment | null;
  }) => void;
  onFocusChange: (focusedInput: 'startDate' | 'endDate' | null) => void;

  // Calendar presentation
  orientation?: 'horizontal' | 'vertical';
  numberOfMonths?: number;
  enableOutsideDays?: boolean;
  daySize?: number;
  isRTL?: boolean;
  firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
  verticalHeight?: number;
  transitionDuration?: number;
  verticalSpacing?: number;
  horizontalMonthPadding?: number;

  // Navigation
  navPosition?: 'navPositionTop' | 'navPositionBottom';
  navPrev?: ReactNode;
  navNext?: ReactNode;
  renderNavPrevButton?: (props: any) => ReactNode;
  renderNavNextButton?: (props: any) => ReactNode;
  onPrevMonthClick?: (newMonth: moment.Moment) => void;
  onNextMonthClick?: (newMonth: moment.Moment) => void;

  // Day customization
  renderCalendarDay?: (props: any) => ReactNode;
  renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode;
  renderMonthElement?: (props: any) => ReactNode;
  renderMonthText?: (month: moment.Moment) => ReactNode;
  renderWeekHeaderElement?: (day: string) => ReactNode;
  minimumNights?: number;
  isDayBlocked?: (day: moment.Moment) => boolean;
  isOutsideRange?: (day: moment.Moment) => boolean;
  isDayHighlighted?: (day: moment.Moment) => boolean;

  // Interaction
  keepOpenOnDateSelect?: boolean;
  onDayClick?: (day: moment.Moment, event: SyntheticEvent) => void;
  onDayMouseEnter?: (day: moment.Moment, event: SyntheticEvent) => void;
  onDayMouseLeave?: (day: moment.Moment, event: SyntheticEvent) => void;

  // Internationalization
  monthFormat?: string;
  weekDayFormat?: string;
  phrases?: object;
  dayAriaLabelFormat?: string;

  // Initial state
  initialVisibleMonth?: () => moment.Moment;

  // Focus management
  isFocused?: boolean;
  showKeyboardShortcuts?: boolean;
  onBlur?: () => void;
  onTab?: (event: SyntheticEvent) => void;
  onShiftTab?: (event: SyntheticEvent) => void;
}

Usage Examples:

import React, { useState } from "react";
import { DayPickerRangeController } from "react-dates";
import moment from "moment";

// Basic embedded calendar
function EmbeddedRangeCalendar() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [focusedInput, setFocusedInput] = useState('startDate');

  return (
    <div style={{ width: '100%', maxWidth: 600 }}>
      <DayPickerRangeController
        startDate={startDate}
        endDate={endDate}
        onDatesChange={({ startDate, endDate }) => {
          setStartDate(startDate);
          setEndDate(endDate);
        }}
        focusedInput={focusedInput}
        onFocusChange={setFocusedInput}
        numberOfMonths={2}
        minimumNights={1}
      />
    </div>
  );
}

// Custom calendar with business logic
function BookingCalendar({ unavailableDates, rates }) {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [focusedInput, setFocusedInput] = useState('startDate');

  const isDayBlocked = (day) => {
    return unavailableDates.some(blockedDay => 
      moment(blockedDay).isSame(day, 'day')
    );
  };

  const renderDayContents = (day, modifiers) => {
    const rate = rates[day.format('YYYY-MM-DD')];
    return (
      <div>
        <div>{day.format('D')}</div>
        {rate && <div style={{ fontSize: '10px' }}>${rate}</div>}
      </div>
    );
  };

  return (
    <div>
      <DayPickerRangeController
        startDate={startDate}
        endDate={endDate}
        onDatesChange={({ startDate, endDate }) => {
          setStartDate(startDate);
          setEndDate(endDate);
          
          // Custom business logic
          if (startDate && endDate) {
            console.log('Total nights:', endDate.diff(startDate, 'days'));
          }
        }}
        focusedInput={focusedInput}
        onFocusChange={setFocusedInput}
        isDayBlocked={isDayBlocked}
        renderDayContents={renderDayContents}
        minimumNights={2}
        numberOfMonths={3}
      />
    </div>
  );
}

DayPickerSingleDateController

Calendar-only component for single date selection without input fields. Ideal for embedded date pickers and custom interfaces.

/**
 * Headless single date calendar controller without input fields
 * @param props - DayPickerSingleDateController configuration
 * @returns Calendar component for single date selection
 */
function DayPickerSingleDateController(props: DayPickerSingleDateControllerProps): ReactElement;

interface DayPickerSingleDateControllerProps {
  // Date state
  date?: moment.Moment | null;
  focused?: boolean;

  // Required callbacks
  onDateChange: (date: moment.Moment | null) => void;
  onFocusChange: ({ focused }: { focused: boolean }) => void;

  // Calendar presentation
  orientation?: 'horizontal' | 'vertical';
  numberOfMonths?: number;
  enableOutsideDays?: boolean;
  daySize?: number;
  isRTL?: boolean;
  firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
  verticalHeight?: number;
  transitionDuration?: number;
  verticalSpacing?: number;
  horizontalMonthPadding?: number;

  // Navigation
  navPosition?: 'navPositionTop' | 'navPositionBottom';
  navPrev?: ReactNode;
  navNext?: ReactNode;
  renderNavPrevButton?: (props: any) => ReactNode;
  renderNavNextButton?: (props: any) => ReactNode;
  onPrevMonthClick?: (newMonth: moment.Moment) => void;
  onNextMonthClick?: (newMonth: moment.Moment) => void;

  // Day customization
  renderCalendarDay?: (props: any) => ReactNode;
  renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode;
  renderMonthElement?: (props: any) => ReactNode;
  renderMonthText?: (month: moment.Moment) => ReactNode;
  renderWeekHeaderElement?: (day: string) => ReactNode;
  isDayBlocked?: (day: moment.Moment) => boolean;
  isOutsideRange?: (day: moment.Moment) => boolean;
  isDayHighlighted?: (day: moment.Moment) => boolean;

  // Interaction
  keepOpenOnDateSelect?: boolean;
  onDayClick?: (day: moment.Moment, event: SyntheticEvent) => void;
  onDayMouseEnter?: (day: moment.Moment, event: SyntheticEvent) => void;
  onDayMouseLeave?: (day: moment.Moment, event: SyntheticEvent) => void;

  // Internationalization
  monthFormat?: string;
  weekDayFormat?: string;
  phrases?: object;
  dayAriaLabelFormat?: string;

  // Initial state
  initialVisibleMonth?: () => moment.Moment;

  // Focus management
  isFocused?: boolean;
  showKeyboardShortcuts?: boolean;
  onBlur?: () => void;
  onTab?: (event: SyntheticEvent) => void;
  onShiftTab?: (event: SyntheticEvent) => void;
}

Usage Examples:

import React, { useState } from "react";
import { DayPickerSingleDateController } from "react-dates";
import moment from "moment";

// Simple embedded calendar
function EmbeddedSingleCalendar() {
  const [date, setDate] = useState(null);
  const [focused, setFocused] = useState(true);

  return (
    <div style={{ width: 300 }}>
      <DayPickerSingleDateController
        date={date}
        onDateChange={setDate}
        focused={focused}
        onFocusChange={({ focused }) => setFocused(focused)}
        numberOfMonths={1}
      />
    </div>
  );
}

// Appointment scheduler calendar
function AppointmentCalendar({ availableSlots, onDateSelect }) {
  const [selectedDate, setSelectedDate] = useState(null);
  const [focused, setFocused] = useState(true);

  const isDayBlocked = (day) => {
    // Block days without available slots
    const dayKey = day.format('YYYY-MM-DD');
    return !availableSlots[dayKey] || availableSlots[dayKey].length === 0;
  };

  const renderDayContents = (day, modifiers) => {
    const dayKey = day.format('YYYY-MM-DD');
    const slots = availableSlots[dayKey];
    const availableCount = slots ? slots.length : 0;
    
    return (
      <div style={{ textAlign: 'center' }}>
        <div>{day.format('D')}</div>
        {availableCount > 0 && (
          <div style={{ fontSize: '10px', color: 'green' }}>
            {availableCount} slots
          </div>
        )}
      </div>
    );
  };

  const handleDateChange = (date) => {
    setSelectedDate(date);
    onDateSelect(date);
  };

  return (
    <div>
      <h3>Select Appointment Date</h3>
      <DayPickerSingleDateController
        date={selectedDate}
        onDateChange={handleDateChange}
        focused={focused}
        onFocusChange={({ focused }) => setFocused(focused)}
        isDayBlocked={isDayBlocked}
        renderDayContents={renderDayContents}
        isOutsideRange={(day) => moment().diff(day) > 0} // No past dates
        numberOfMonths={2}
      />
    </div>
  );
}

Public Methods

Both controller components expose these public methods via refs:

// Navigation methods
onDayClick(day: moment.Moment, event: SyntheticEvent): void;
onDayMouseEnter(day: moment.Moment, event?: SyntheticEvent): void;
onDayMouseLeave(day: moment.Moment, event?: SyntheticEvent): void;
onPrevMonthClick(newMonth?: moment.Moment): void;
onNextMonthClick(newMonth?: moment.Moment): void;

// Focus management
getFirstFocusableDay(newMonth: moment.Moment): moment.Moment;

Common Patterns

Custom Date Validation

// Combined validation function
const isDateUnavailable = (day) => {
  const isPastDate = moment().diff(day) > 0;
  const isWeekend = day.day() === 0 || day.day() === 6;
  const isHoliday = holidays.includes(day.format('YYYY-MM-DD'));
  
  return isPastDate || isWeekend || isHoliday;
};

// Usage
<DayPickerSingleDateController
  isDayBlocked={isDateUnavailable}
  // ... other props
/>

Custom Day Rendering

// Complex day rendering with multiple data types
const renderDayContents = (day, modifiers) => {
  const hasEvents = events[day.format('YYYY-MM-DD')];
  const isHighlighted = modifiers.has('highlighted');
  
  return (
    <div className={`custom-day ${isHighlighted ? 'highlighted' : ''}`}>
      <span className="day-number">{day.format('D')}</span>
      {hasEvents && <div className="event-indicator" />}
    </div>
  );
};

Month Navigation Handling

// Custom month navigation with logging
const handlePrevMonth = (newMonth) => {
  console.log('Navigated to:', newMonth.format('MMMM YYYY'));
  // Custom logic here
};

const handleNextMonth = (newMonth) => {
  console.log('Navigated to:', newMonth.format('MMMM YYYY'));
  // Custom logic here
};

Install with Tessl CLI

npx tessl i tessl/npm-react-dates

docs

calendar-components.md

calendar-controllers.md

index.md

input-components.md

main-pickers.md

utilities.md

tile.json