Обычно мы, как разработчики, не изобретаем велосипед. В большинстве случаев мы просто используем стороннюю библиотеку для календаря. Но в некоторых случаях сторонний календарь не подходит для нашего дизайна и функциональности. Так что, когда выбора нет, нужно придумать свой календарь.

Давайте начнем, ниже приведены пакеты, которые мы собираемся использовать.

Я не буду подробно объяснять приведенную выше библиотеку, пожалуйста, обратитесь по ссылке, если вы не знакомы с приведенной выше библиотекой.

Настройте свой проект React, выполнив команду ниже

  • npx create-react-app custom-calendar && cd custom-calendar
  • npm install dates-generator --save
  • npm install styled-components --save

styled-components используется для применения стилей css к компонентам, и мне проще писать стили css, используя styled-components

Теперь давайте отредактируем /src/App.js

import React, { useState, useEffect } from 'react';
import styled from 'styled-components'

const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const Container = styled.div`
  width: 300px;
  border: 1px solid black;
  margin: 0 auto;
  box-shadow: 10px 10px 0px black;
`

const MonthText = styled.div`
  font-size: 26px;
  font-weight: bold;
  text-align: center;
`

const App = () => {
  const [selectedDate, setSelectedDate] = useState(new Date());
  const [dates, setDates] = useState([]);
  const [calendar, setCalendar] = useState({
    month: selectedDay.getMonth(),
    year: selectedDay.getFullYear(),
  });


  useEffect(() => {}, [])

  return (
    <div style={{ width: '100%', paddingTop: 50 }}>
      <Container>
        <MonthText>
          {months[calendar.month]}
        </MonthText>
      </Container>
    </div>
  );
}

export default App;

Если вы запустите это, вы увидите, что текущий месяц отображается в вашем браузере.

selectedDate — это дата, которую мы выбрали в календаре. По умолчанию дата является текущей датой.

dates — это состояние, в котором будут храниться все даты данного месяца.

calendar — это состояние, в котором будут храниться месяц и год для календаря.

Теперь давайте заполним календарь датами, по умолчанию календарь будет календарем текущего месяца.

...

const [calendar, setCalendar] = useState({
    month: selectedDay.getMonth(),
    year: selectedDay.getFullYear(),
});

useEffect(() => {
  const body = {
    month: calendar.month,
    year: calendar.year
  };
  const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

  setDates([ ...dates ]);
  setCalendar({
    ...calendar,
    nextMonth,
    nextYear,
    previousMonth,
    previousYear
  });
}, [])

...

Как видите, мы добавили useEffect внутрь нашего компонента. Внутри useEffect мы запускаем функцию datesGenerator, предоставляемую dates-generator.

Эта функция вернет все даты, доступные для данного месяца. Мы предоставили месяц и год, передав body внутри функции datesGenerator. Мы можем использовать атрибуты previousMonth/nextMonth и previousYear/nextYear, чтобы получить календарные даты предыдущего/следующего месяца.

Подробнее о том, как работает dates-generator, читайте здесь

Поскольку сейчас мы уже храним все даты в состоянии, давайте отобразим его в нашем браузере.

const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

...

return (
  <div style={{ width: '100%', paddingTop: 50 }}>
    <Container>
      <MonthText>
        {months[calendar.month]}
      </MonthText>
      <div>

        <div>
          <table style={{ width: '100%' }}>
            <tbody>
              <tr>
                {days.map((day) => (
                  <td key={day} style={{ padding: '5px 0' }}>
                    <div style={{ textAlign: 'center', padding: '5px 0' }}>
                      {day}
                    </div>
                   </td>
                 ))}
              </tr>

              {dates.length > 0 && dates.map((week) => (
                <tr key={JSON.stringify(week[0])}>
                  {week.map((each) => (
                    <td key={JSON.stringify(each)} style={{ padding: '5px 0' }}>
                      <div style={{ textAlign: 'center', padding: '5px 0' }}>
                        {each.date}
                      </div>
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>

      </div>
    </Container>
  </div>
);

...

Я добавил переменную days для отображения поверх дат. Если вы сейчас посмотрите в свой браузер, то увидите календарь на текущий месяц.

Теперь давайте напишем еще 3 функции:

  • onClickNext - перейти к календарю на следующий месяц
  • onClickPrevious - перейти к календарю предыдущего месяца
  • onSelectDate - установить выбранную пользователем дату
const onClickNext = () => {
    const body = { month: calendar.nextMonth, year: calendar.nextYear };
    const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

    setDates([ ...dates ]);
    setCalendar({
      ...calendar,
      month: calendar.nextMonth,
      year: calendar.nextYear,
      nextMonth,
      nextYear,
      previousMonth,
      previousYear
    });
  }

  const onClickPrevious = () => {
    const body = { month: calendar.previousMonth, year: calendar.previousYear };
    const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

    setDates([ ...dates ]);
    setCalendar({
      ...calendar,
      month: calendar.previousMonth,
      year: calendar.previousYear,
      nextMonth,
      nextYear,
      previousMonth,
      previousYear
    });
  }

  const onSelectDate = (date) => {
    setSelectedDate(new Date(date.year, date.month, date.date))
  }

...
return (
  ...
    <div style={{ padding: 10 }}>
      <div onClick={onClickPrevious} style={{ float: 'left', width: '50%' }}>
        Previous
      </div>
      <div onClick={onClickNext} style={{ float: 'left', width: '50%', textAlign: 'right' }}>
        Next
      </div>
    </div>
    <MonthText>
      {months[calendar.month]}
    </MonthText>

    ...    
      <div onClick={() => onSelectDate(each.jsDate)} style={{ textAlign: 'center', padding: '5px 0' }}>
        {each.date}
      </div>
    ...

    <div style={{ padding: 10 }}>
      Selected Date: {selectedDate.toDateString()}
    </div>
  </Container>
  ...
)
...

Теперь дата, которую вы нажмете, будет отображаться в нижней части календаря. если вы посмотрите в свой браузер, вы увидите этот календарь:

Вот и все, теперь у вас есть базовый календарь, вы можете настроить его по своему усмотрению. Это все от меня.

Полный код для /App.js можно получить по этому адресу здесь

Обратная связь приветствуется.

Первоначально опубликовано на https://dev.to 4 мая 2020 г.