JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 243
  • Score
    100M100P100Q50220F
  • License Unlicense

React component that renders a date picker with support for date ranges

Package Exports

  • @acusti/date-picker

Readme

@acusti/date-picker

latest version downloads per month bundle size supply chain security

A comprehensive React date picker library with support for single date selection, date ranges, and two-up month calendar views. Built with accessibility and user experience in mind, featuring smooth navigation, intelligent date range handling, and customizable styling.

Key Features

  • Single & Range Selection - Pick individual dates or date ranges with intelligent range logic
  • Two-Up Calendar View - Display two months side-by-side for easier range selection
  • Month Navigation - Smooth navigation with optional month limits for business logic
  • Smart Range Handling - Automatic date swapping and preview highlighting
  • Flexible Date Input - Accepts Date objects, ISO strings, or timestamps
  • Customizable Display - Month abbreviations, custom styling, and layout options
  • Built-in Styling - Attractive default styles with CSS custom property theming
  • Accessibility Ready - Keyboard navigation and screen reader support

Installation

npm install @acusti/date-picker
# or
yarn add @acusti/date-picker

Quick Start

import { DatePicker } from '@acusti/date-picker';
import { useState } from 'react';

// Simple single date picker
function SimpleDatePicker() {
    const [selectedDate, setSelectedDate] = useState('');

    return (
        <DatePicker
            onChange={({ dateStart }) => setSelectedDate(dateStart)}
            dateStart={selectedDate}
        />
    );
}

// Date range picker
function DateRangePicker() {
    const [dateRange, setDateRange] = useState({ start: '', end: '' });

    const handleChange = ({ dateStart, dateEnd }) => {
        setDateRange({ start: dateStart, end: dateEnd || '' });
    };

    return (
        <DatePicker
            isRange
            isTwoUp
            onChange={handleChange}
            dateStart={dateRange.start}
            dateEnd={dateRange.end}
            useMonthAbbreviations
        />
    );
}

API Reference

DatePicker Component

type Props = {
    /** Additional CSS class name for styling */
    className?: string;

    /** End date for range selection (Date object, ISO string, timestamp, or null) */
    dateEnd?: Date | string | number | null;

    /** Start date for single or range selection (Date object, ISO string, timestamp, or null) */
    dateStart?: Date | string | number | null;

    /** Initial month to display (number of months since January 1970) */
    initialMonth?: number;

    /** Enable date range selection mode */
    isRange?: boolean;

    /** Display two months side-by-side */
    isTwoUp?: boolean;

    /** Earliest month that can be navigated to */
    monthLimitFirst?: number;

    /** Latest month that can be navigated to */
    monthLimitLast?: number;

    /** Callback when dates are selected */
    onChange: (payload: {
        dateEnd?: string | null;
        dateStart: string;
    }) => void;

    /** Show end date’s month initially (when both start and end dates exist) */
    showEndInitially?: boolean;

    /** Use abbreviated month names (Jan, Feb, etc.) */
    useMonthAbbreviations?: boolean;
};

MonthCalendar Component

For advanced use cases, you can use the individual month calendar:

import { MonthCalendar } from '@acusti/date-picker';

type MonthCalendarProps = {
    className?: string;
    dateEnd?: Date | string | number | null;
    dateEndPreview?: string | null;
    dateStart?: Date | string | number | null;
    isRange?: boolean;
    month: number; // Months since January 1970
    onChange?: (date: string) => void;
    onChangeEndPreview?: (date: string) => void;
    title?: string;
};

Utility Functions

import {
    getMonthFromDate,
    getYearFromMonth,
    getMonthNameFromMonth,
    getMonthAbbreviationFromMonth,
} from '@acusti/date-picker';

// Convert Date to month number (months since Jan 1970)
const monthNumber = getMonthFromDate(new Date());

// Convert month number to calendar year
const year = getYearFromMonth(monthNumber);

// Get full month name
const monthName = getMonthNameFromMonth(monthNumber); // "January"

// Get abbreviated month name
const monthAbbr = getMonthAbbreviationFromMonth(monthNumber); // "Jan"

Usage Examples

Booking System Date Range

🎮 Live Demo

import { DatePicker } from '@acusti/date-picker';
import { useState } from 'react';

function BookingDatePicker() {
    const [checkIn, setCheckIn] = useState('');
    const [checkOut, setCheckOut] = useState('');
    const isValid = checkIn && checkOut;

    // Limit to next 12 months only
    const today = new Date();
    const monthLimitFirst = getMonthFromDate(today);
    const monthLimitLast = monthLimitFirst + 12;

    return (
        <div className="booking-date-picker">
            <h3>Select Your Stay</h3>
            <DatePicker
                className="booking-date-picker-story"
                isRange
                isTwoUp
                monthLimitFirst={monthLimitFirst}
                monthLimitLast={monthLimitLast}
                onChange={({ dateStart, dateEnd }) => {
                    setCheckIn(dateStart);
                    setCheckOut(dateEnd ?? '');
                }}
                dateStart={checkIn}
                dateEnd={checkOut}
                useMonthAbbreviations
            />

            {isValid ? (
                <div
                    className="booking-summary"
                    style={{
                        marginTop: '16px',
                        padding: '16px',
                        border: '1px solid #e1e5e9',
                        borderRadius: '8px',
                        backgroundColor: '#f8f9fa',
                    }}
                >
                    <p>
                        <strong>Check-in:</strong>{' '}
                        {new Date(checkIn).toLocaleDateString()}
                    </p>
                    <p>
                        <strong>Check-out:</strong>{' '}
                        {new Date(checkOut).toLocaleDateString()}
                    </p>
                    <p>
                        <strong>Duration:</strong>{' '}
                        {(() => {
                            const checkInTime = new Date(
                                checkIn,
                            ).getTime();
                            const checkOutTime = new Date(
                                checkOut,
                            ).getTime();
                            const diffTime = checkOutTime - checkInTime;
                            const diffDays = Math.ceil(
                                diffTime / (1000 * 60 * 60 * 24),
                            );
                            return Math.max(0, diffDays);
                        })()}{' '}
                        nights
                    </p>
                </div>
            ) : null}
        </div>
    );
}

Event Scheduler

🎮 Live Demo

import { DatePicker } from '@acusti/date-picker';
import { useState } from 'react';

function EventScheduler() {
    const [eventDate, setEventDate] = useState('');
    const [showPicker, setShowPicker] = useState(false);

    // Only allow future dates
    const monthLimitFirst = getMonthFromDate(new Date());

    return (
        <div className="event-scheduler">
            <div style={{ marginBottom: '16px' }}>
                <label
                    htmlFor="event-date"
                    style={{
                        display: 'block',
                        marginBottom: '8px',
                        fontWeight: 500,
                    }}
                >
                    Event Date:
                </label>
                <input
                    id="event-date"
                    type="text"
                    value={
                        eventDate
                            ? new Date(eventDate).toLocaleDateString()
                            : ''
                    }
                    onClick={() => setShowPicker(true)}
                    placeholder="Click to select date"
                    readOnly
                    style={{
                        padding: '8px 12px',
                        border: '2px solid #e1e5e9',
                        borderRadius: '6px',
                        cursor: 'pointer',
                        width: '200px',
                    }}
                />
            </div>

            {showPicker ? (
                <div
                    style={{
                        position: 'relative',
                        zIndex: 1000,
                        marginTop: '8px',
                    }}
                >
                    <div
                        style={{
                            padding: '16px',
                            border: '1px solid #e1e5e9',
                            borderRadius: '8px',
                            backgroundColor: 'white',
                            boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
                        }}
                    >
                        <DatePicker
                            className="event-scheduler-story"
                            monthLimitFirst={monthLimitFirst}
                            onChange={({ dateStart }) => {
                                setEventDate(dateStart);
                                setShowPicker(false);
                            }}
                            dateStart={eventDate}
                        />
                        <button
                            onClick={() => setShowPicker(false)}
                            style={{
                                marginTop: '12px',
                                padding: '8px 16px',
                                border: '1px solid #ccc',
                                borderRadius: '4px',
                                cursor: 'pointer',
                            }}
                        >
                            Cancel
                        </button>
                    </div>
                </div>
            ) : null}
        </div>
    );
}

Report Date Range Filter

import { DatePicker } from '@acusti/date-picker';
import { useState, useEffect } from 'react';

function ReportFilter() {
    const [dateRange, setDateRange] = useState({
        start: '',
        end: '',
    });
    const [isOpen, setIsOpen] = useState(false);

    // Limit to past 2 years for historical reports
    const today = new Date();
    const monthLimitLast = getMonthFromDate(today);
    const monthLimitFirst = monthLimitLast - 24;

    const handleApplyRange = ({ dateStart, dateEnd }) => {
        setDateRange({
            start: dateStart,
            end: dateEnd || dateStart,
        });

        if (dateEnd) {
            setIsOpen(false);
        }
    };

    const formatDateRange = () => {
        if (!dateRange.start) return 'Select date range';

        const startDate = new Date(dateRange.start).toLocaleDateString();
        const endDate = dateRange.end
            ? new Date(dateRange.end).toLocaleDateString()
            : startDate;

        return `${startDate} - ${endDate}`;
    };

    return (
        <div className="report-filter">
            <button
                className="date-range-button"
                onClick={() => setIsOpen(!isOpen)}
            >
                📅 {formatDateRange()}
            </button>

            {isOpen ? (
                <div className="date-picker-dropdown">
                    <DatePicker
                        isRange
                        isTwoUp
                        monthLimitFirst={monthLimitFirst}
                        monthLimitLast={monthLimitLast}
                        onChange={handleApplyRange}
                        dateStart={dateRange.start}
                        dateEnd={dateRange.end}
                        showEndInitially={!!dateRange.end}
                    />
                </div>
            ) : null}
        </div>
    );
}

Birthday Picker with Year Limits

🎮 Live Demo

import { DatePicker, getMonthFromDate } from '@acusti/date-picker';
import { useState } from 'react';

function BirthdayPicker() {
    const [birthday, setBirthday] = useState('');

    // Reasonable age limits: 13 to 120 years ago
    const today = new Date();
    const maxAge = new Date(
        today.getFullYear() - 120,
        today.getMonth(),
        today.getDate(),
    );
    const minAge = new Date(
        today.getFullYear() - 13,
        today.getMonth(),
        today.getDate(),
    );

    const monthLimitFirst = getMonthFromDate(maxAge);
    const monthLimitLast = getMonthFromDate(minAge);

    // Start showing calendar at a reasonable age (25 years ago)
    const defaultMonth = getMonthFromDate(
        new Date(
            today.getFullYear() - 25,
            today.getMonth(),
            today.getDate(),
        ),
    );

    return (
        <div className="birthday-picker">
            <h3>Enter Your Birthday</h3>
            <DatePicker
                initialMonth={defaultMonth}
                monthLimitFirst={monthLimitFirst}
                monthLimitLast={monthLimitLast}
                onChange={({ dateStart }) => setBirthday(dateStart)}
                dateStart={birthday}
            />

            {birthday ? (
                <p
                    style={{
                        marginTop: '16px',
                        padding: '12px',
                        backgroundColor: '#e3f2fd',
                        borderRadius: '6px',
                    }}
                >
                    <strong>
                        You are{' '}
                        {Math.floor(
                            (today.getTime() -
                                new Date(birthday).getTime()) /
                                (1000 * 60 * 60 * 24 * 365.25),
                        )}{' '}
                        years old
                    </strong>
                </p>
            ) : null}
        </div>
    );
}

Multi-Month Navigation

🎮 Live Demo

import { DatePicker, getMonthFromDate } from '@acusti/date-picker';
import { useState } from 'react';

function FlexibleDatePicker() {
    const [selectedDate, setSelectedDate] = useState('');
    const [viewMode, setViewMode] = useState<'single' | 'double'>(
        'single',
    );

    return (
        <div className="flexible-date-picker">
            <div style={{ marginBottom: '16px' }}>
                <label style={{ marginRight: '16px' }}>
                    <input
                        type="radio"
                        checked={viewMode === 'single'}
                        onChange={() => setViewMode('single')}
                        style={{ marginRight: '4px' }}
                    />
                    Single Month
                </label>
                <label>
                    <input
                        type="radio"
                        checked={viewMode === 'double'}
                        onChange={() => setViewMode('double')}
                        style={{ marginRight: '4px' }}
                    />
                    Two Months
                </label>
            </div>

            <DatePicker
                isTwoUp={viewMode === 'double'}
                useMonthAbbreviations={viewMode === 'double'}
                onChange={({ dateStart }) => setSelectedDate(dateStart)}
                dateStart={selectedDate}
            />

            {selectedDate ? (
                <div
                    style={{
                        marginTop: '16px',
                        padding: '12px',
                        border: '1px solid #e1e5e9',
                        borderRadius: '6px',
                    }}
                >
                    <strong>Selected:</strong>{' '}
                    {new Date(selectedDate).toLocaleDateString()}
                    <br />
                    <strong>Day of week:</strong>{' '}
                    {new Date(selectedDate).toLocaleDateString('en', {
                        weekday: 'long',
                    })}
                </div>
            ) : null}
        </div>
    );
}

Custom Month Calendar Usage

import { MonthCalendar, getMonthFromDate } from '@acusti/date-picker';
import { useState } from 'react';

function CustomCalendarGrid() {
    const [selectedDates, setSelectedDates] = useState<string[]>([]);
    const currentMonth = getMonthFromDate(new Date());

    const handleDateSelect = (date: string) => {
        setSelectedDates((prev) =>
            prev.includes(date)
                ? prev.filter((d) => d !== date)
                : [...prev, date],
        );
    };

    return (
        <div>
            <h3>Multi-Select Calendar</h3>
            <p>Click dates to select/deselect multiple dates</p>

            <MonthCalendar
                month={currentMonth}
                onChange={handleDateSelect}
                title="Select Multiple Dates"
            />

            <div className="selected-dates">
                <h4>Selected Dates ({selectedDates.length}):</h4>
                <ul>
                    {selectedDates.map((date) => (
                        <li key={date}>
                            {new Date(date).toLocaleDateString()}
                        </li>
                    ))}
                </ul>
            </div>
        </div>
    );
}

Styling

The date picker uses CSS custom properties for easy theming:

.date-picker {
    /* Calendar colors */
    --date-picker-bg: #ffffff;
    --date-picker-border: #e0e0e0;
    --date-picker-text: #333333;

    /* Selected date colors */
    --date-picker-selected-bg: #007bff;
    --date-picker-selected-text: #ffffff;

    /* Range selection colors */
    --date-picker-range-bg: #e3f2fd;
    --date-picker-range-border: #2196f3;

    /* Hover states */
    --date-picker-hover-bg: #f5f5f5;

    /* Navigation arrows */
    --date-picker-arrow-color: #666666;
    --date-picker-arrow-hover: #333333;

    /* Month header */
    --date-picker-header-text: #333333;
    --date-picker-header-bg: #f8f9fa;
}

/* Custom styling example */
.booking-calendar {
    --date-picker-selected-bg: #28a745;
    --date-picker-range-bg: #d4edda;
    --date-picker-range-border: #28a745;
}

.event-calendar {
    --date-picker-selected-bg: #6f42c1;
    --date-picker-range-bg: #e2d9f3;
}

Month Number System

The date picker uses an internal month numbering system where months are represented as the number of months since January 1970:

  • January 1970 = 0
  • February 1970 = 1
  • January 2024 = 648
  • etc.

This system allows for efficient month calculations and navigation. The utility functions handle the conversion between this system and standard dates.

Browser Compatibility

  • Modern Browsers - Chrome, Firefox, Safari, Edge (latest versions)
  • Mobile Support - iOS Safari, Android Chrome
  • SSR Compatible - Works with Next.js, React Router, and other SSR frameworks

Common Use Cases

  • Booking Systems - Hotels, flights, rental properties
  • Event Management - Conference registration, appointment scheduling
  • Reporting Tools - Date range filters for analytics
  • Form Inputs - Birthday selection, deadline setting
  • Content Management - Publishing date selection
  • E-commerce - Delivery date selection, sale periods
  • Project Management - Milestone and deadline tracking

Demo

See the Storybook documentation and examples for interactive demonstrations of all date picker features and configurations.