'use client'; import { type ChangeEvent } from 'react'; import * as React from 'react'; import { Icon12Dropdown, Icon20ChevronLeftOutline, Icon20ChevronRightOutline, } from '@vkontakte/icons'; import { classNames } from '@vkontakte/vkjs'; import { DEFAULT_MAX_YEAR, DEFAULT_MIN_YEAR, getMonths, getYears } from '../../lib/calendar'; import { addMonths, setMonth, setYear, subMonths } from '../../lib/date'; import type { HTMLAttributesWithRootRef } from '../../types'; import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; import { useConfigProvider } from '../ConfigProvider/ConfigProviderContext'; import { CustomSelect, type SelectProps } from '../CustomSelect/CustomSelect'; import { RootComponent } from '../RootComponent/RootComponent'; import { Tappable } from '../Tappable/Tappable'; import { Paragraph } from '../Typography/Paragraph/Paragraph'; import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden'; import styles from './CalendarHeader.module.css'; type ArrowMonthProps = Omit, 'onClick' | 'aria-label'>; export type CalendarHeaderTestsProps = { /** * Передает атрибут `data-testid` для дропдауна выбора месяца в заголовке календаря. */ monthDropdownTestId?: string | ((monthIndex: number) => string); /** * Передает атрибут `data-testid` для дропдауна выбора года в заголовке календаря. */ yearDropdownTestId?: string | ((year: number) => string); /** * Передает атрибут `data-testid` для кнопки перехода к следующему месяцу в заголовке календаря. */ nextMonthButtonTestId?: string; /** * Передает атрибут `data-testid` для кнопки перехода к предыдущему месяцу в заголовке календаря. */ prevMonthButtonTestId?: string; }; export interface CalendarHeaderProps extends Omit, 'onChange'>, CalendarHeaderTestsProps { /** * Отображаемая дата. */ viewDate: Date; /** * Скрывает иконку для переключения на предыдущий месяц. */ prevMonthHidden?: boolean; /** * Скрывает иконку для переключения на следующий месяц. */ nextMonthHidden?: boolean; /** * Отключает селекторы выбора месяца/года. */ disablePickers?: boolean; /** * `aria-label` для кнопки предыдущего месяца. */ prevMonthLabel?: string; /** * `aria-label` для кнопки следующего месяца. */ nextMonthLabel?: string; /** * `aria-label` для селектора месяца. */ changeMonthLabel?: string; /** * `aria-label` для селектора года. */ changeYearLabel?: string; /** * Кастомная иконка для кнопки предыдущего месяца. */ prevMonthIcon?: React.ReactNode; /** * Кастомная иконка для кнопки следующего месяца. */ nextMonthIcon?: React.ReactNode; /** * Дополнительные свойства для кнопки предыдущего месяца. */ prevMonthProps?: ArrowMonthProps; /** * Дополнительные свойства для кнопки следующего месяца. */ nextMonthProps?: ArrowMonthProps; /** * Функция для проверки блокировки месяца. */ isMonthDisabled?: (monthNumber: number, year?: number) => boolean; /** * Функция для проверки блокировки года. */ isYearDisabled?: (yearNumber: number) => boolean; /** * Обработчик изменения отображаемой даты. */ onChange: (viewDate: Date) => void; /** * Нажатие на кнопку переключения на следующий месяц. */ onNextMonth?: () => void; /** * Нажатие на кнопку переключения на предыдущий месяц. */ onPrevMonth?: () => void; } export const CalendarHeader = ({ viewDate, onChange, prevMonthHidden: prevMonthHiddenProp = false, nextMonthHidden: nextMonthHiddenProp = false, disablePickers = false, onNextMonth, onPrevMonth, prevMonthProps = {}, nextMonthProps = {}, prevMonthLabel = 'Предыдущий месяц', nextMonthLabel = 'Следующий месяц', changeMonthLabel = 'Изменить месяц', changeYearLabel = 'Изменить год', prevMonthIcon = ( ), nextMonthIcon = ( ), isMonthDisabled, isYearDisabled, monthDropdownTestId, yearDropdownTestId, prevMonthButtonTestId, nextMonthButtonTestId, ...restProps }: CalendarHeaderProps): React.ReactNode => { const { locale, direction } = useConfigProvider(); const onMonthsChange = React.useCallback( (_: ChangeEvent, newValue: SelectProps['value']) => onChange(setMonth(viewDate, Number(newValue))), [onChange, viewDate], ); const onYearChange = React.useCallback( (_: ChangeEvent, newValue: SelectProps['value']) => onChange(setYear(viewDate, Number(newValue))), [onChange, viewDate], ); const currentYear = viewDate.getFullYear(); const currentMonth = viewDate.getMonth(); const months = React.useMemo( () => getMonths(locale).map(({ value, label }) => ({ value, label: {label}, disabled: isMonthDisabled && isMonthDisabled(value), })), [locale, isMonthDisabled], ); const years = React.useMemo( () => getYears(currentYear, 100).map((year) => ({ ...year, disabled: isYearDisabled && isYearDisabled(year.value), })), [currentYear, isYearDisabled], ); const formatter = new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', }); const { className: prevMonthClassName, ...restPrevMonthProps } = prevMonthProps; const { className: nextMonthClassName, ...restNextMonthProps } = nextMonthProps; let nextMonthHidden = nextMonthHiddenProp || (currentMonth === 11 && currentYear === DEFAULT_MAX_YEAR); if (isMonthDisabled && !nextMonthHidden) { nextMonthHidden = isMonthDisabled( currentMonth === 11 ? 0 : currentMonth + 1, currentMonth === 11 ? Math.min(currentYear + 1, DEFAULT_MAX_YEAR) : currentYear, ); } let prevMonthHidden = prevMonthHiddenProp || (currentMonth === 0 && currentYear === DEFAULT_MIN_YEAR); if (isMonthDisabled && !prevMonthHidden) { prevMonthHidden = isMonthDisabled( currentMonth === 0 ? 11 : currentMonth - 1, currentMonth === 0 ? Math.max(currentYear - 1, DEFAULT_MIN_YEAR) : currentYear, ); } const stopPropogationOfEscapeKeyboardEventWhenSelectIsOpen = React.useCallback( (event: React.KeyboardEvent, isOpen: boolean) => { if (isOpen && event.key === 'Escape') { event.stopPropagation(); } }, [], ); return ( {!prevMonthHidden && ( {prevMonthLabel}, {formatter.format(subMonths(viewDate, 1))} {direction === 'ltr' ? prevMonthIcon : nextMonthIcon} )} {disablePickers ? ( {new Intl.DateTimeFormat(locale, { month: 'long', }).format(viewDate)}   {new Intl.DateTimeFormat(locale, { year: 'numeric', }).format(viewDate)} ) : (
} onChange={onMonthsChange} forceDropdownPortal={false} selectType="accent" aria-label={changeMonthLabel} data-testid={ typeof monthDropdownTestId === 'string' ? monthDropdownTestId : monthDropdownTestId?.(currentMonth) } onInputKeyDown={stopPropogationOfEscapeKeyboardEventWhenSelectIsOpen} /> } onChange={onYearChange} forceDropdownPortal={false} selectType="accent" aria-label={changeYearLabel} data-testid={ typeof yearDropdownTestId === 'string' ? yearDropdownTestId : yearDropdownTestId?.(currentYear) } onInputKeyDown={stopPropogationOfEscapeKeyboardEventWhenSelectIsOpen} />
)} {!nextMonthHidden && ( {nextMonthLabel}, {formatter.format(addMonths(viewDate, 1))} {direction === 'ltr' ? nextMonthIcon : prevMonthIcon} )}
); };