Giter Site home page Giter Site logo

tamazlykar / datepicker Goto Github PK

View Code? Open in Web Editor NEW

This project forked from rehookify/datepicker

1.0 0.0 0.0 484 KB

The ultimate tiny tool for creating date and range pickers in your React applications.

Home Page: https://rehookify.com/

License: MIT License

Shell 0.08% JavaScript 1.87% TypeScript 93.79% CSS 3.74% HTML 0.52%

datepicker's Introduction

@rehookify/datepicker

The ultimate tiny tool for creating date and range pickers in your React applications.

size npm twitter discord

#StandWithUkraine πŸ’™πŸ’›

We have war at our home πŸ‡ΊπŸ‡¦

Help us in our struggle, πŸ’° United24, KOLO, Come Back Alive

Features

  • 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.

Install

npm i -S @rehookify/datepicker

Quickstart: modular use

With modular hooks

import { useState } from 'react';
import { useDatePickerState } 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.date}`}>
            <button>{dpDay.day}</button>
          </li>
        ))}
      </ul>
    </section>
  );
}

With modular context

import { useState } from 'react';
import {
  DatePickerStateProvider,
  useContextCalendars
} from '@rehookify/datepicker';

const DatePicker = () => {
  const { calendars, weekDays } = useContextCalendars();

  const { year, month, 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.date}`}>
            <button>{dpDay.day}</button>
          </li>
        ))}
      </ul>
    </section>
  )
}

const App = () => {
  const [selectedDates, onDatesChange] = useState<Date[]>([]);
  return (
    <DatePickerStateProvider
      config={{
        selectedDates,
        onDatesChange,
        dates: { mode: 'multiple' },
      }}
    >
      <DatePicker />
    </DatePickerStateProvider>
  );
}

Quickstart: everything in one place

With hook

import React, { 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()}>&lt;</button>
          <p>{month} {year}</p>
          <button {...nextMonthButton()}>&gt;</button>
        </div>
        <ul>
          {weekDays.map((day) => (<li key={`${month}-${day}`}>{day}</li>))}
        </ul>
      </header>
      <ul>
        {days.map((dpDay) => (
          <li key={`${month}-${dpDay.date}`}>
            <button
              {...dayButton(dpDay, { onClick: onDayClick })}
            >
              {dpDay.day}
            </button>
          </li>
        ))}
      </ul>
    </section>
  )
}

With context

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>
  );
}

API reference

State

The state consists of three parts: data, propGetters and actions.

Data

The data represents all entities that you could use in your date picker. It consists of calendars, weekDays, months, years and selectedDates

interface Data {
  calendars: Calendar[];
  weekDays: string[],
  months: CalendarMonth[],
  years: CalendarYears[],
  selectedDates: Date[],
}

calendars

calendars are an array of objects with year, month and days properties. The calendars array always has at least one member.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'
  | 'will-be-in-range'
  | 'will-be-range-start'
  | 'will-be-range-end'
  | '';

interface CalendarDay {
  $date: Date;
  date: string;
  day: string;
  isToday: boolean;
  range: DayRange;
  disabled: boolean;
  selected: boolean;
  inCurrentMonth: boolean;
}

interface Calendar {
  year: string;
  month: string;
  days: CalendarDay[];
}

weekDays

Weekdays are an array of names [Mon, Tue, Wed, ...]. The name format can be changed by locale.weekdays property πŸ‘€ Locale configuration

type Weekdays = string[]

months

Months are an array of objects with $date, name, isSelected and isActive properties. The name format could be changed by locale.monthName property πŸ‘€ Locale configuration.

interface CalendarMonth {
  $date: Date;
  name: string;
  disabled: boolean;
  active: boolean;
  selected: boolean;
}

selected - shaws that we have a date selected for this month.

active - shows that a user sees this month as current.

years

Years are an array of objects with $date, value, isSelected, and isActive properties.

interface CalendarYear {
  $date: Date;
  value: number;
  disabled: boolean;
  active: boolean;
  selected: boolean;
}

selected - shows that we have a date selected for this year.

active - shows that a user sees this year as current.

selectedDates

An array of raw dates

type SelectedDates = Date[];

formattedDates

An array of formatted dates date.toLocaleDateString(locale, options) πŸ‘€ Locale configuration

type FormattedDates = string[];

Prop-Getters

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

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 πŸ‘† #Calendars
  • props?: PropsGetterConfig

Returns:

interface DayButtonReturnValue extends PropGetterReturnValue {
  onMouseEnter?(): void;
}

✏️ NOTE: onMouseMove - appears only if dates mode is range, it is not composable. πŸ‘€ Dates configuration

monthButton

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 πŸ‘† Months
  • props?: PropsGetterConfig

nextMonthButton

nextMonthButton moves months pagination one step forward.

Params:

  • props?: PropsGetterConfig

previousMonthButton

previousMonthButton moves months pagination one step backward.

Params:

  • props?: PropsGetterConfig

yearButton

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 πŸ‘† Years
  • props?: PropsGetterConfig

nextYearsButton

nextYearsButton moves years pagination one step forward.

Params:

  • props?: PropsGetterConfig

✏️ NOTE: onClick - callback function doesn't get date as a second parameter.

previousYearsButton

previousYearsButton moves years pagination one step backward.

Params:

  • props?: PropsGetterConfig

✏️ NOTE: onClick - callback function doesn't get date as a second parameter.

Actions

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

setMonth - set the month that a user sees.

Params:

  • date: Date - javascript Date object, you could get it from the month.$date πŸ‘† Months, or create new Date(2022, 10, 18)

setNextMonth

setNextMonth adds one month to current

setPreviousMonth

setPreviousMonth subtracts one month from current

setYear

setYear set the year that user sees

Params:

  • date: Date - javascript Date object, you could get it from the year.$date πŸ‘† Years, or create new Date(2022, 10, 18)

setNextYears

setNextYears moves years pagination one step forward

setPreviousYears

setPreviousYears moves years pagination one step backward

Configuration

useDatePicker, DatePickerProvider, useDatePickerState and DatePickerStateProvider accepts same configuration object that consists of locale, calendar, dates and years

Default configuration

{
  selectedDates: [],
  onDatesChange: undefined,
  locale: {
    locale: 'en-GB',
    day: '2-digit',
    year: 'numeric',
    weekday: 'short',
    monthName: 'long',
  },
  calendar: {
    mode: 'static',
    offsets: [0],
  },
  dates: {
    mode: 'single',
    selectedDates: [],
    minDate: null,
    maxDate: null,
    toggle: false,
    limit: undefined,
  },
  years: {
    mode: 'decade',
    numberOfYears: 12;
    step: 10,
  },
}

General configuration

selectedDates: Date[];
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,
})

Locale configuration

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'];
}
  • 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 how selectedDates will formatted.
  • day: "2-digit" | "numeric" | undefined - defines the date's format in Calendars
  • year: "numeric" | "2-digit" | undefined - defines the year's format in Years
  • monthName: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined - defines the moths format in Months
  • weekday: "long" | "short" | "narrow" | undefined - defines weekday's format in Weekdays

Calendar configuration

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.

Dates configuration

interface DatesUserConfig {
  mode?: 'single' | 'multiple' | 'range';
  minDate?: Date;
  maxDate?: Date;
  selectedDates?: Date | Date[];
  toggle?: boolean;
  limit?: number;
}
  • mode: 'single' | 'multiple' | 'range' - defines how date picker behaves with days

single - a user can pick only 1 date

multiple - a user can pick unlimited number of dates until limit is set

range - a user can pick one dates range. selectedDates will have 2 dates

  • minDate: Date - all dates in prop-getters before the minDate will be marked as disabled.

✏️ NOTE: if minDate > NOW - initial calendar will show the month with minDate

  • maxDate: Date - all dates in prop-getters after the maxDate will be marked as disabled.

✏️ NOTE: if maxDate < NOW - initial calendar will show the month with maxDate

  • selectedDates: Date | Date[] - dates that will be added to selectedDates state.

✏️ NOTE: If mode: 'single' - after the first click selectedDates will be reset to 1 date.

  • toggle: boolean - allows a user to unselect dates.
  • limit: number - number of dates that a user could select.

✏️ NOTE: works only with mode: 'multiple'

Years configuration

type YearsMode = 'decade' | 'fluid';

interface YearsConfig {
  mode: YearsMode,
  numberOfYears: number;
  step: number;
},
  • numberOfYears: number - the number of years you want to show to a user.
  • mode: 'decade' | 'fluid' - it defines how current year will be centered;

✏️ NOTE: difference between decade and fluid mode

Years matrix for decade mode;

It will count current decade (for 2022 is 2020-2029) and adds end of the previous and start of the next decade

2019 2020 2021
2022 2023 2024
2025 2026 2027
2028 2029 2030

Years matrix for fluid mode;

It will place current year in the middle of the list -1 (we want to look at the future more) πŸ˜‰

2017 2018 2019
2020 2021 2022
2023 2024 2025
2026 2027 2028
  • step: number - it defines step for previous/nextYearsButton

Modular Hooks

The main aim of modular hooks is to safe bundle size of your app.

All entities are consists of 3 hooks: data, prop-getters and actions (for example useDays, useDaysPropGetters and useDaysActions).

useDatePickerState

export interface State {
  rangeEnd: Date | null;
  selectedDates: Date[];
  offsetDate: Date;
  offsetYear: number;
  config: DatePickerConfig;
}

export type Action =
  | SelectDateAction
  | SetOffsetDate
  | SetYearAction
  | SetRangeEndAction;

type UseDatePickerState = (config: DatePickerConfig) =>
  [State, Dispatch<Action>]

Under the hook, it uses useReducer to capture the entire date-picker state and provides dispatch for state manipulation.

Modular hooks use state and dispatch to derive their entities and update the date-picker.

DatePickerStateProvider uses this hook and propagates state and dispatch through context.

type DatePickerStateProviderValue = {
  s: State;
  d: Dispatch<Action>
}

useCalendars

type UseCalendars = (state: State) => {
  calendars: Calendar[];
  weekDays: string[];
}

Basic entities to build UI without interactivity.

useDays

type UseDays = (state: State) => {
  selectedDates: Date[];
  formattedDates: string[];
};

Set of data with raw and formatted dates

useDaysPropGetters

type UseDaysPropGetters = (state: State, dispatch: Dispatch<Action>) => {
  dayButton(day: CalendarDay, config: PropsGetterConfig): void;
};

Prop-getter for dates selection.

useDaysActions

type UseDaysActions = (dispatch: Dispatch<Action>) => {
  setDay(Date): void;
  setRangeEnd(Date | null): void;
};

Set of actions for dates manipulation.

useMonths

type UseMonths = (state: State, dispatch: Dispatch<Action>) => {
  months: CalendarMonth[],
};

Months data.

useMonthsPropGetters

type UseMonthsPropGetters = (state: State, dispatch: Dispatch<Action>) => {
  monthButton(month: CalendarMonth, config: PropsGetterConfig): void,
  nextMonthButton(config: PropsGetterConfig): void,
  previousMonthButton(config: PropsGetterConfig): void,
};

Prop-getters for month manipulation.

useMonthsActions

type UseMonthsActions = (state: State, dispatch: Dispatch<Action>) => {
  setMonth(date: Date): void,
  setNextMonth(): void,
  setPreviousMonth(): void,
};

Actions for month manipulation.

useYears

type UseYears = (state: State, dispatch: Dispatch<Action>) => {
  years: CalendarYear[]
};

Years data.

useYearsPropGetters

type UseYearsPropGetters = (state: State, dispatch: Dispatch<Action>) => {
  yearButton(year: CalendarYear, config: PropsGetterConfig): void;
  nextYearsButton(config: PropsGetterConfig): void;
  previousYearsButton(config: PropsGetterConfig): void;
};

Prop-getters for years manipulation.

useYearsActions

type UseYearsActions = (state: State, dispatch: Dispatch<Action>) => {
  setYear(date: Date): void;
  setNextYears(): void;
  setPreviousYears(): void;
};

Actions for years manipulation.

Context Hooks

We have set of context hooks that have similar API with regular one.

The main difference that they use context value from the DatePickerStateProvider. You don't need to pass any parameters to them.

✏️ NOTE: You can use them only inside DatePickerStateProvider! πŸ‘€ With modular context

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.