Паттерны
Статичная сетка месяца
Когда использовать:
- достаточно одного известного месяца, например при выборе дня внутри расчётного периода
Заметки по композиции:
- используйте
DayPickerLayoutи передавайтеmonthявно, чтобы сетка не зависела от сегодняшней даты - ограничивайте период через
fromиto, а не скрывайте дни
Компромиссы:
- просто и стабильно
- нет навигации; используйте скроллер или
Calendar, когда пользователи переходят между месяцами
import { DayPicker, DayPickerCell, DayPickerLayout, Text } from '@vega-ui/react';
import { useState } from 'react';
const dateFormat = new Intl.DateTimeFormat('en-US', { month: 'long', day: 'numeric', timeZone: 'UTC' });
export default function StaticMonthGrid() {
const [day, setDay] = useState<string | null>(null);
return (
<div style={{ display: 'grid', gap: 'var(--spacing-6)', justifyItems: 'start', width: '100%' }}>
<DayPicker
aria-label='Payment day, November 2026'Перелистывание месяцев свайпом
Когда использовать:
- пользователи переходят между месяцами свайпом или постраничной прокруткой, а приложение рисует собственный заголовок
Заметки по композиции:
- используйте
DayPickerScrollerсDayPickerScrollerContentиDayPickerScrollerLayout onChangePeriodсообщает первый день месяца, прокрученного в область видимости; сохраните его и передайте обратно какmonth, чтобы заголовок и сетка совпадали- храните в состоянии и
activeи переносите его в новый месяц, иначе сетка может потерять точку табуляции - берите
Calendar, когда достаточно стандартного заголовка и представлений месяца и года
Компромиссы:
- тактильно, и возможна любая раскладка заголовка
- приложение само управляет состоянием навигации, которое взял бы на себя
Calendar
import {
DayPicker,
DayPickerCell,
DayPickerScroller,
DayPickerScrollerContent,
DayPickerScrollerLayout,
Icon,
IconButton,
Text,
} from '@vega-ui/react';
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import { useState } from 'react';Собственный рендер сетки
Когда использовать:
- должна измениться сама структура строк и ячеек, чего не может шаблон ячейки
Заметки по композиции:
- стройте строки через
createDayPickerGrid, а не пишите математику дат заново; передавайте тот жеcalendar, что и компоненту dateравенundefinedдля пустой ячейки за пределами месяца;day— номер дня в календарной системе- задавайте
disabledиexcludedячейкам вне месяца, как это делает встроенная ячейка - здесь сетка с понедельника по пятницу: ячейки выходных не рендерятся, а недели без рабочих дней месяца отброшены
Компромиссы:
- полный контроль над разметкой
- подписи, начало недели и обработка дней соседних месяцев — задача приложения; если меняется только содержимое, предпочитайте шаблон ячейки
import {
CalendarBaseWeekLabel,
CalendarBaseWeekLabels,
DayPicker,
DayPickerItem,
DayPickerRow,
DayPickerRowGroup,
createDayPickerGrid,
} from '@vega-ui/react';
const MONTH = '2026-11-01';
const WORK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];Шаблон ячейки
Когда использовать:
- ячейкам дней нужно дополнительное содержимое, например цена, статус или счётчик
Заметки по композиции:
- дочерние элементы
DayPickerLayoutиDayPickerScrollerLayoutрендерятся по разу на каждый день внутри контекста ячейки - читайте день через
useDayPickerCellContext():{ col, date, fields, label, inMonth };dateравенundefinedдля пустой ячейки за пределами месяца - рендерите содержимое внутри
DayPickerCell, чтобы ячейка сохраняла состояния выбора, фокуса и отключения - берите номера дней из
labelилиfields, а не из ISO-строкиdate; в негригорианских календарях они различаются - ищите данные по
date— он одинаков в любой календарной системе - чисто визуальному содержимому (точки, цвет) нужны слова в
aria-labelячейки
Компромиссы:
- сохраняет сетку, ключи и поведение клавиатуры раскладки
- насыщенные ячейки могут теснить содержимое на малых размерах
import {
DayPicker,
DayPickerCell,
DayPickerLayout,
useDayPickerCellContext,
} from '@vega-ui/react';
const events: Record<string, string[]> = {
'2026-11-04': ['Design review', 'Sprint planning'],
'2026-11-12': ['Release 3.2'],
'2026-11-18': ['Team offsite', 'Retro', 'Demo day'],
};Сравнение
Быстрое правило выбора
Используйте DayPicker, когда нужно низкоуровневое поведение выбора в сетке дней. Используйте Calendar, когда нужен календарь более высокого уровня.
DayPicker или Calendar
- Используйте
DayPickerдля низкоуровневой композиции сетки дней. - Используйте
Calendarдля календарной подсистемы более высокого уровня.
DayPicker или MonthPicker / YearPicker
- Используйте
DayPickerдля конкретных дат. - Используйте
MonthPickerилиYearPickerдля выбора месяца или года.
Выбирайте этот компонент, когда
- вы расширяете внутренности семейства календарей
- нужен выбор в сетке дней с собственной композицией
Не выбирайте этот компонент, когда
- нужна календарная абстракция более высокого уровня с меньшим объёмом ручной композиции
Решение проблем
Отрендеренный месяц не совпадает с ожидаемым периодом
Симптом
Интерфейс показывает сетку месяца, но не тот период, который ожидали пользователи.
Вероятная причина
Видимый month, календарная система calendar или defaultMonth скроллера не согласованы.
Как проверить
- изучите фактические
month,calendarи значение, переданное вonChangePeriod
Решение
Используйте один месяц YYYY-MM-DD как источник истины и передавайте один и тот же calendar каждой части, которая его рендерит.
Собственный рендер дня теряет наглядность выбора или фокуса
Симптом
Даты по-прежнему работают логически, но пользователи не могут понять, какой день активен или выбран.
Вероятная причина
Собственный рендер отбросил важное визуальное состояние.
Как проверить
- проверьте состояния фокуса, выбора, отключения и исключения в собственной раскладке
Решение
Сохраните визуальные признаки состояний из стандартного паттерна элемента дня.
Прокручиваемое перелистывание месяцев работает ненадёжно
Симптом
Свайп между месяцами иногда работает, но подписи периода или фокус ведут себя нестабильно.
Вероятная причина
Сопоставление периода в скроллере и отрендеренное содержимое месяца недостаточно плотно синхронизированы.
Как проверить
- логируйте месяц, переданный в
onChangePeriod, во время перелистывания
Решение
Выводите подписи из месяца, который сообщает onChangePeriod, а не вычисляйте его по индексу скроллера.