'use client'; import { useRef } from 'react'; import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { Keys, pressedKey } from '../../lib/accessibility'; import { setHours, setMinutes } from '../../lib/date'; import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; import { Button, type ButtonProps } from '../Button/Button'; import { CalendarTimePicker } from './CalendarTimePicker'; import styles from './CalendarTime.module.css'; export type CalendarTimeTestsProps = { /** * Передает атрибут `data-testid` для дропдауна выбора часа в календаре. */ hoursTestId?: string | undefined; /** * Передает атрибут `data-testid` для дропдауна выбора минут в календаре. */ minutesTestId?: string | undefined; /** * Передает атрибут `data-testid` для кнопки "Готово" в календаре. */ doneButtonTestId?: string | undefined; }; export type CalendarDoneButtonProps = { /** * Кастомное отображение кнопки `"Done"`. */ DoneButton?: React.ComponentType | undefined; /** * Текст отображаемый в кнопке `"Done"`. */ doneButtonText?: string | undefined; /** * Управление отображением кнопки `"Done"`. */ doneButtonShow?: boolean | undefined; /** * Блокировка взаимодействия с кнопкой "Done". */ doneButtonDisabled?: boolean | undefined; /** * Обработки нажатия на кнопку `"Done"`. */ onDoneButtonClick?: (() => void) | undefined; }; export interface CalendarTimeProps extends CalendarTimeTestsProps, CalendarDoneButtonProps { /** * Отображаемая дата. */ value: Date; /** * Текст выпадающего списка с выбором часов. Делает его доступным для ассистивных технологий. */ changeHoursLabel?: string | undefined; /** * Текст выпадающего списка с выбором минут. Делает его доступным для ассистивных технологий. */ changeMinutesLabel?: string | undefined; /** * Обработчик изменения времени. */ onChange?: ((value: Date) => void) | undefined; /** * Функция установки часа (для таймзонно-зависимого календаря). */ setHours?: ((date: Date, hours: number) => Date) | undefined; /** * Функция установки минут (для таймзонно-зависимого календаря). */ setMinutes?: ((date: Date, minutes: number) => Date) | undefined; /** * Функция для проверки блокировки выбора даты и времени. */ isDayDisabled?: ((day: Date, withTime?: boolean) => boolean) | undefined; } const hours: Array<{ value: number; label: string; }> = []; for (let i = 0; i < 24; i += 1) { hours.push({ value: i, label: String(i).padStart(2, '0') }); } const minutes: Array<{ value: number; label: string; }> = []; for (let i = 0; i < 60; i += 1) { minutes.push({ value: i, label: String(i).padStart(2, '0') }); } export const CalendarTime = ({ value, onChange, onDoneButtonClick, changeHoursLabel, changeMinutesLabel, setHours: setHoursFn = setHours, setMinutes: setMinutesFn = setMinutes, isDayDisabled, doneButtonText = 'Готово', doneButtonDisabled = false, doneButtonShow = true, minutesTestId, hoursTestId, doneButtonTestId, DoneButton, }: CalendarTimeProps): React.ReactNode => { const hoursInputRef = useRef(null); const minutesInputRef = useRef(null); const doneButtonRef = useRef(null); const localHours = isDayDisabled ? hours.map((hour) => { return { ...hour, disabled: isDayDisabled(setHoursFn(value, hour.value), true) }; }) : hours; const localMinutes = isDayDisabled ? minutes.map((minute) => { return { ...minute, disabled: isDayDisabled(setMinutesFn(value, minute.value), true) }; }) : minutes; const onPickerKeyDown = (e: React.KeyboardEvent) => { const key = pressedKey(e); /* Мы хотим иметь возможность быстро, по Enter перемещаться между * селектами с часами и минутами, также как мы это делаем по нажатию на Tab */ if (key !== Keys.ENTER) { return; } const steps = [hoursInputRef, minutesInputRef, doneButtonRef].filter((ref) => Boolean(ref.current), ); const currentStepIndex = steps.findIndex((step) => step.current === e.target); const nextStepIndex = currentStepIndex + 1; if (nextStepIndex >= steps.length) { return; } const nextStep = steps[nextStepIndex]; if (nextStep.current) { e.preventDefault(); nextStep.current?.focus(); } }; const stopPropagationOfEscapeKeyboardEventWhenSelectIsOpen = React.useCallback( (event: React.KeyboardEvent, isOpen: boolean) => { if (isOpen && event.key === 'Escape') { event.stopPropagation(); } }, [], ); const onSelectInputKeyDown = (e: React.KeyboardEvent, isOpen: boolean) => { onPickerKeyDown(e); stopPropagationOfEscapeKeyboardEventWhenSelectIsOpen(e, isOpen); }; const renderDoneButton = () => { const ButtonComponent = DoneButton ?? Button; return ( {doneButtonText} ); }; return (
v.getHours()} onChange={onChange} options={localHours} setTime={setHoursFn} onInputKeyDown={onSelectInputKeyDown} inputRef={hoursInputRef} inputLabel={changeHoursLabel} inputTestId={hoursTestId} />
:
v.getMinutes()} onChange={onChange} options={localMinutes} setTime={setMinutesFn} onInputKeyDown={onSelectInputKeyDown} inputRef={minutesInputRef} inputLabel={changeMinutesLabel} inputTestId={minutesTestId} /> {doneButtonShow && (
{renderDoneButton()}
)}
); };