useCalendar для календарей

useCalendar — это хук из библиотеки React Aria, предназначенный для управления логикой календаря и обеспечения доступности компонентов, отображающих даты. Он отделяет логику выбора даты, управления фокусом и навигации от визуальной реализации, позволяя создавать собственные UI-компоненты с поддержкой стандартов доступности (ARIA).


Подключение и базовое использование

Для использования useCalendar необходимо импортировать соответствующие хуки из @react-aria/calendar и @react-stately/calendar:

import { useCalendar } from '@react-aria/calendar';
import { useCalendarState } from '@react-stately/calendar';
import { createCalendar } from '@internationalized/date';

useCalendar тесно связан с состоянием календаря, которое управляется через useCalendarState. Этот стейт хранит выбранную дату, текущий фокус и логику навигации между месяцами и годами.


Инициализация состояния календаря

const state = useCalendarState({
  locale: 'ru-RU',
  createCalendar,
  visibleDuration: { months: 1 }
});

Пояснения параметров:

  • locale — локаль для отображения месяцев и дней недели.
  • createCalendar — функция из @internationalized/date, создающая объект календаря.
  • visibleDuration — определяет, сколько месяцев отображается одновременно. Обычно это один месяц, но можно расширить на несколько.

Использование useCalendar

useCalendar принимает объект со свойствами состояния и возвращает атрибуты для контейнера календаря и отдельных дней:

const { calendarProps, prevButtonProps, nextButtonProps } = useCalendar({ 
  'aria-label': 'Календарь', 
  state 
});
  • calendarProps — атрибуты, которые нужно передать корневому элементу календаря. Включают ARIA-атрибуты для доступности.
  • prevButtonProps и nextButtonProps — свойства для кнопок навигации между месяцами.

Навигация и управление фокусом

useCalendar обеспечивает корректную работу клавиатуры:

  • ArrowLeft / ArrowRight — перемещение фокуса по дням.
  • ArrowUp / ArrowDown — перемещение на неделю вверх/вниз.
  • Home / End — переход к началу или концу недели.
  • PageUp / PageDown — переход к предыдущему или следующему месяцу.

Фокус всегда остаётся в пределах текущего месяца, что предотвращает потерю контекста пользователем с ограниченными возможностями.


Настройка выбранной даты

Состояние календаря управляет выбранной датой через объект state.selectedDate. Изменение даты осуществляется функцией state.setSelectedDate(date). Пример интеграции с пользовательским компонентом дня:

{state.visibleDays.map(day => (
  <button
    key={day.toString()}
    {...state.getDayProps(day)}
    className={state.isSelected(day) ? 'selected' : ''}
  >
    {day.day}
  </button>
))}
  • state.visibleDays — массив объектов дат текущего месяца.
  • state.getDayProps(day) — возвращает необходимые ARIA-атрибуты для дня.
  • state.isSelected(day) — проверяет, выбрана ли дата.

Поддержка диапазонов и ограничения

useCalendarState поддерживает выбор диапазона и ограничения:

const state = useCalendarState({
  locale: 'ru-RU',
  createCalendar,
  minValue: new Date(2024, 0, 1),
  maxValue: new Date(2024, 11, 31),
  visibleDuration: { months: 1 }
});
  • minValue и maxValue ограничивают возможные даты.
  • Для диапазона можно использовать useRangeCalendarState, но интерфейс useCalendar и методы остаются аналогичными.

Локализация и форматирование дат

Для корректного отображения дней недели и месяцев в useCalendar важно использовать locale и createCalendar из @internationalized/date. Например:

import { CalendarDate } from '@internationalized/date';

const today = new CalendarDate();
const formatted = today.toString(); // формат по локали
  • Поддерживаются разные календари (григорианский, японский, буддистский и др.).
  • Локализация влияет на порядок дней недели, формат даты и названия месяцев.

Комбинирование с пользовательским UI

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

<div {...calendarProps}>
  <button {...prevButtonProps}>Назад</button>
  <button {...nextButtonProps}>Вперед</button>
  <div className="days-grid">
    {state.visibleDays.map(day => (
      <button key={day.toString()} {...state.getDayProps(day)}>
        {day.day}
      </button>
    ))}
  </div>
</div>
  • ARIA-атрибуты обеспечивают правильное взаимодействие с экранными читалками.
  • Стейт и логика календаря полностью отделены от внешнего вида.

Оптимизация производительности

При больших календарях или отображении нескольких месяцев:

  • Использовать visibleDuration для ограничения числа рендеримых месяцев.
  • Применять мемоизацию компонентов дней с React.memo.
  • Обрабатывать события клавиатуры централизованно для предотвращения лишних перерендеров.

Взаимодействие с другими хуками React Aria

useCalendar легко интегрируется с:

  • useCalendarGrid — для работы с сеткой дней.
  • useCalendarCell — для отдельного дня с учетом ARIA.
  • useLocale — для динамического изменения локали.
  • useFocusRing — для визуальной индикации фокуса.

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