The ultimate tiny tool for creating date, range and time pickers in your React applications.
We have war at our home πΊπ¦
Help us in our struggle, π° United24, KOLO, Come Back Alive
- Small size.
- Zero dependencies.
- Modular Hooks will help you to use only what you need.
- You can get accessible component props with prop-getters.
- You have full power to manipulate the state with actions.
- Available as a hook or context.
- Support localization with
.toLocaleString
,.toLocalTimeString
npm i -S @rehookify/datepicker
π Check the Examples
import { useState } from 'react';
import { useDatePickerState, useCalendars } from '@rehookify/datepicker';
const DatePicker = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const dpState = useDatePickerState({
selectedDates,
onDatesChange,
dates: { toggle: true, mode: 'multiple' },
});
const { calendars, weekDays } = useCalendars(dpState);
const { month, year, days } = calendars[0];
return (
<section>
<header>
<div>
<p>{month} {year}</p>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button>{dpDay.day}</button>
</li>
))}
</ul>
</section>
);
}
import { useState } from 'react';
import {
DatePickerStateProvider,
useContextCalendars,
useContextDaysPropGetters,
useContextTime,
useContextTimePropGetters,
} from '@rehookify/datepicker';
const DatePicker = () => {
const { calendars, weekDays } = useContextCalendars();
const { dayButton } = useContextDaysPropGetters();
const { year, month, days } = calendars[0];
return (
<main>
<header>
<div>
<p>{month} {year}</p>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button {...dayButton(dpDay)}>{dpDay.day}</button>
</li>
))}
</ul>
</main>
)
}
const TimePicker = () => {
const { time } = useContextTime();
const { timeButton } = useContextTimePropGetters();
return (
<ul>
{time.map((t) => (
<li key={t.$date.toString()}>
<button {...timeButton(t)}>{t.time}</>
</li>
))}
</ul>
)
}
const App = () => {
const d = new Date();
const [selectedDates, onDatesChange] = useState<Date[]>([d]);
return (
<DatePickerStateProvider
config={{
selectedDates,
focusDate: d,
onDatesChange,
dates: { mode: 'multiple' },
}}
>
<section>
<DatePicker />
<TimePicker />
</section>
</DatePickerStateProvider>
);
}
import { MouseEvent, useState } from 'react';
import { useDatePicker } from '@rehookify/datepicker';
const DatePicker = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const {
data: { weekDays, calendars },
propGetters: {
dayButton,
previousMonthButton,
nextMonthButton,
},
} = useDatePicker({
selectedDates,
onDatesChange,
});
// calendars[0] is always present, this is an initial calendar
const { year, month, days } = calendars[0];
const onDayClick = (evt: MouseEvent<HTMLElement>, date: Date) => {
// In case you need any action with evt
evt.stopPropagation();
// In case you need any additional action with date
console.log(date);
}
// selectedDates is an array of dates
// formatted with date.toLocaleDateString(locale, options)
return (
<section>
{selectedDates.length > 0 && <h1>{selectedDates[0]}</h1>}
<header>
<div>
<button {...previousMonthButton()}><</button>
<p>{month} {year}</p>
<button {...nextMonthButton()}>></button>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button
{...dayButton(dpDay, { onClick: onDayClick })}
>
{dpDay.day}
</button>
</li>
))}
</ul>
</section>
)
}
import { useState } from 'react';
import {
DatePickerProvider,
useDatePickerContext,
} from '@rehookify/datepicker';
const DatePicker = () => {
const {
data: { weekDays, calendars, years, months },
} = useDatePickerContext();
const { year, month, days } = calendars[0];
return (
<section>
<header>{month} {year}</header>
...
</section>
)
}
const App = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
return (
<DatePickerProvider
config={{
selectedDates,
onDatesChange,
dates: { mode: 'range' },
}}
>
<DatePicker />
</DatePickerProvider>
);
}
The state consists of three main parts: data, propGetters and actions.
The data represents all entities that you could use in your date picker. It consists of calendars, weekDays, months, years, selectedDates and time
interface Data {
calendars: Calendar[];
formattedDates: Date[];
months: CalendarMonth[];
selectedDates: Date[];
time: Time[];
weekDays: string[];
years: CalendarYears[];
}
calendars
are an array of objects with year, month and days properties. It always has at least one member - an initial calendar calendars[0]
. For calendars configuration π Calendar config
export type DayRange =
| 'in-range'
| 'range-start'
| 'range-end'
| 'range-start range-end'
| 'will-be-in-range'
| 'will-be-range-start'
| 'will-be-range-end'
| '';
interface CalendarDay {
$date: Date;
day: string;
disabled: boolean;
inCurrentMonth: boolean;
now: boolean;
range: DayRange;
selected: boolean;
}
interface Calendar {
days: CalendarDay[];
month: string;
year: string;
}
Weekdays are an array of day names [Mon
, Tue
, Wed
, ...]. The name format can be changed by locale.weekdays
property π Locale configuration
type Weekdays = string[]
Months are an array of objects with $date, active, disabled, month, now and selected properties. The month name format could be changed by locale.monthName
property π Locale configuration.
interface CalendarMonth {
$date: Date;
active: boolean;
disabled: boolean;
month: string;
now: boolean;
selected: boolean;
}
active
- shows that a user sees this month as current.
month
- month name e.g 'December'
now
- shows that this month is current in real life
selected
- shaws that we have a date selected for this month.
Years are an array of objects with $date, active, disabled, now, selected and year properties.
interface CalendarYear {
$date: Date;
active: boolean;
disabled: boolean;
now: boolean;
selected: boolean;
year: number;
}
active
- shows that a user sees this year as current.
now
- shows that this year is current in real life
selected
- shows that we have a date selected for this year.
year
- year value e.g 2023
An array of raw dates
type SelectedDates = Date[];
An array of formatted dates date.toLocaleDateString(locale, options)
π Locale configuration
type FormattedDates = string[];
Time is an array of objects with $date, disabled, now, selected and value properties. You can change time format with hour12
, hour
and minute
options π Locale configuration
export interface Time {
$date: Date;
disabled: boolean;
selected: boolean;
time: string;
}
time
- time value e.g 15:30
or 3:30 pm
A prop-getters is a pattern that allows you to get all the necessary pops and logic for your components. It gives you the possibility to pass additional configuration. @rehookify/datepicker
composes onClick
and calls it with event and date - onClick(event, date)
.
Each prop-getter accepts a configuration object to enhance the properties and functionality of the component.
interface PropsGetterConfig extends Record<string, unknown> {
onClick?(evt?: MouseEvent<HTMLElement>, date?: Date): void;
disabled?: boolean;
}
Each prop-getter returns an object with properties:
interface PropGetterReturnValue extends Omit<PropsGetterConfig, 'onClick' | 'disabled'>{
role: 'button',
tabIndex: 0,
disabled: boolean,
'area-disabled': boolean;
onClick?(evt: MouseEvent<HTMLElement>),
}
dayButton
produces properties for calendar days and sets the selectedDates
state when a user clicks on a day.
Params:
day: Calendar
- you could get it from the calendars π #Calendarsprops?: PropsGetterConfig
Returns:
interface DayButtonReturnValue extends PropGetterReturnValue {
onMouseEnter?(): void;
}
βοΈ NOTE: onMouseMove
- appears only if dates mode is range
, it is not composable. π Dates configuration
monthButton
produces properties for calendar months and changes month when a user clicks on a month.
Params:
month: CalendarMonth
- you could get it from the months π Monthsprops?: PropsGetterConfig
nextMonthButton
moves months pagination forward. You can specify steps by passing them to configuration. nextMonthButton({ step: 12 })
Params:
props?: MonthPropsGetterConfig
previousMonthButton
moves months pagination backward. You can specify steps by passing them to configuration. previousMonthButton({ step: 3 })
Params:
props?: MonthPropsGetterConfig
yearButton
produces properties for calendar years and changes the year when user clicks on a year.
Params:
year: CalendarYear
- you could get it from the years π Yearsprops?: PropsGetterConfig
nextYearsButton
moves years pagination one step forward.
Params:
props?: PropsGetterConfig
βοΈ NOTE: onClick
- callback function doesn't get date
as a second parameter.
previousYearsButton
moves years pagination one step backward.
Params:
props?: PropsGetterConfig
βοΈ NOTE: onClick
- callback function doesn't get date
as a second parameter.
timeButton
produces properties for time button and changes corresponding selectedDate
and focusDate
.
Params:
time: Time
- you could get it from the years π Timeprops?: PropsGetterConfig
Params:
props?: PropsGetterConfig
βοΈ NOTE: onClick
- callback function doesn't get date
as a second parameter.
Actions allow you to control the date picker's state. They don't have any additional logic. You need to check the state of days, months and years or disable the months and years pagination buttons.
setMonth
- set the month that a user sees.
Params:
date: Date
- javascript Date object, you could get it from themonth.$date
π Months, or createnew Date(2022, 10, 18)
setNextMonth
adds one month to current
setPreviousMonth
subtracts one month from current
setYear
set the year that user sees
Params:
date: Date
- javascript Date object, you could get it from theyear.$date
π Years, or createnew Date(2022, 10, 18)
setNextYears
moves years pagination one step forward
setPreviousYears
moves years pagination one step backward
useDatePicker
, DatePickerProvider
, useDatePickerState
and DatePickerStateProvider
accepts same configuration object that consists of locale, calendar, dates and years
{
selectedDates: [],
focusDate: null,
onDatesChange: undefined,
dates: {
limit: undefined,
minDate: null,
maxDate: null,
mode: 'single',
selectSameDate: false,
toggle: false,
},
calendar: {
mode: 'static',
offsets: [0],
},
locale: {
locale: 'en-GB',
day: '2-digit',
year: 'numeric',
weekday: 'short',
monthName: 'long',
hour: '2-digit',
minute: '2-digit',
hour12: undefined,
second: undefined,
},
time: {
interval: 30,
minTime: null,
maxTime: null,
},
years: {
mode: 'decade',
numberOfYears: 12;
step: 10,
},
}
selectedDates: Date[];
focusDate: Date | null;
onDatesChange(d: Date[]): void;
The date-picker is a controlled component that utilizes the selectedDates
property to create all entities and display the user's selection. If you don't provide a selectedDates
value, it will default to an empty array, but the selection won't be visible. Every time a date is selected, it will be passed to the onDatesChange
function.
A typical setup is to use the useState
hook to handle updates.
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const { data } = useDatePicker({
selectedDates,
onDatesChange,
})
focusDate
is initial value for the time-picker, if it is null or not present in the selectedDates
array all time buttons will be disabled.
Locale configuration consists of values compatible with date.toLocaleString()
.
For more information about locale you can reed at MDN doc.
interface LocaleConfig {
locale?: Intl.LocalesArgument;
options?: Intl.DateTimeFormatOptions;
day?: Intl.DateTimeFormatOptions['day'];
year?: Intl.DateTimeFormatOptions['year'];
monthName?: Intl.DateTimeFormatOptions['month'];
weekday?: Intl.DateTimeFormatOptions['weekday'];
hour: Intl.DateTimeFormatOptions['hour'];
minute: Intl.DateTimeFormatOptions['minute'];
second?: Intl.DateTimeFormatOptions['second'];
hour12?: Intl.DateTimeFormatOptions['hour12'];
}
locale: UnicodeBCP47LocaleIdentifier | Locale | (UnicodeBCP47LocaleIdentifier | Locale)[] | undefined
- used to format all instances, a string with a BCP 47 language tag.options: Intl.DateTimeFormatOptions
it is left undefined to allow you to control howselectedDates
will formatted.day: "2-digit" | "numeric" | undefined
- defines the date's format in Calendarsyear: "numeric" | "2-digit" | undefined
- defines the year's format in YearsmonthName: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined
- defines the moths format in Monthsweekday: "long" | "short" | "narrow" | undefined
- defines weekday's format in Weekdayshour: "numeric" | "2-digit" | undefined
- defines hours format in Timeminute: "numeric" | "2-digit" | undefined
- defines minutes format in Timesecond: "numeric" | "2-digit" | undefined
- defines seconds format in Timehour12: boolean | undefined
- defines time format in general12:12
or12:12 pm
interface CalendarConfig {
mode?: 'static' | 'fluid';
offsets?: number[];
startDay: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
mode: 'static' | 'fluid'
controls how calendar will look like
Calendars in static
mode have 6 rows by 7 days. This prevents UI from jumping while switching between months and years.
π February 2022 in static
mode:
30 31 01 02 03 04 05
06 07 08 09 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 01 02 03 04 05
06 07 08 09 10 11 12
Calendars in fluid
mode counts start and end offsets.
π February 2022 in fluid
mode:
30 31 01 02 03 04 05
06 07 08 09 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 01 02 03 04 05
offsets: number[]
- adds additional calendars to the Calendars;
The first calendar is always [0]
- offsets comes next.
The values of offsets could be negative, -1
, this will add month before current.
offsets: [-1, 1]
gives you 3 calendars November, October, December
(today is November 2022).
startDay
- The day of the week that will be the first in the calendar. It accepts a number in the range of 0-6, where 0 represents Sunday and 6 represents Saturday.