diff --git a/packages/plasma-hope/src/components/Calendar/Calendar.tsx b/packages/plasma-hope/src/components/Calendar/Calendar.tsx deleted file mode 100644 index 80373cd35b..0000000000 --- a/packages/plasma-hope/src/components/Calendar/Calendar.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; - -import { CalendarDouble } from './CalendarDouble'; -import { CalendarBase } from './CalendarBase'; -import { withRange } from './withRange'; -import type { CalendarBaseProps } from './CalendarBase'; -import type { CalendarDoubleProps } from './CalendarDouble'; -import { CalendarRange } from './types'; - -export type CalendarProps = - | ({ - /** - * Режим отображения: двойной или одинарный. - */ - isDouble?: false; - /** - * Режим выбора: диапазон или одинарный. - */ - isRange?: false; - /** - * Выбранное значение. - */ - value: Date; - } & CalendarBaseProps) - | ({ - /** - * Режим отображения: двойной или одинарный. - */ - isDouble?: false; - /** - * Режим выбора: диапазон или одинарный. - */ - isRange: true; - } & CalendarRange) - | ({ - /** - * Режим отображения: двойной или одинарный. - */ - isDouble?: true; - /** - * Режим выбора: диапазон или одинарный. - */ - isRange?: false; - /** - * Выбранное значение. - */ - value: Date; - } & CalendarDoubleProps) - | ({ - /** - * Режим отображения: двойной или одинарный. - */ - isDouble?: true; - /** - * Режим выбора: диапазон или одинарный. - */ - isRange: true; - } & CalendarRange); - -/** - * Компонент календаря с диапазоном. - */ -export const CalendarBaseRange = withRange(CalendarBase); - -/** - * Компонент двойного календаря c диапазоном. - */ -export const CalendarDoubleRange = withRange(CalendarDouble); - -/** - * Компонент календаря. - */ -export const Calendar: React.FC = ({ ...rest }) => { - if (!rest.isRange && !rest.isDouble) { - return ; - } - - if (!rest.isRange && rest.isDouble) { - return ; - } - - if (rest.isRange && !rest.isDouble) { - return ; - } - - if (rest.isRange && rest.isDouble) { - return ; - } - - return null; -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarBase.tsx b/packages/plasma-hope/src/components/Calendar/CalendarBase.tsx deleted file mode 100644 index 734edfb08b..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarBase.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React, { useCallback, useMemo, useReducer, useState, KeyboardEvent } from 'react'; -import styled from 'styled-components'; - -import type { CalendarStateType, DateObject, Calendar } from './types'; -import { CalendarState, UseKeyNavigationProps } from './types'; -import { isValueUpdate, YEAR_RENDER_COUNT } from './utils'; -import { CalendarDays } from './CalendarDays'; -import { CalendarMonths } from './CalendarMonths'; -import { CalendarHeader } from './CalendarHeader'; -import { CalendarYears } from './CalendarYears'; -import { useKeyNavigation } from './hooks'; -import { getInitialState, reducer } from './store/reducer'; -import { ActionType } from './store/types'; - -export type { CalendarStateType }; - -export type CalendarBaseProps = Calendar & { - /** - * Тип отображения календаря: дни, месяца, года. - */ - type?: CalendarStateType; -}; - -const StyledCalendar = styled.div` - position: relative; - user-select: none; - z-index: 1; - - width: 19.5rem; - height: 19.5rem; -`; - -const IsOutOfRange = styled.div` - position: absolute; - padding: 0; - margin: 0; - height: 0; - width: 0; - border: 0; - overflow: hidden; - clip: rect(0 0 0 0); -`; - -/** - * Компонент календаря. - */ -export const CalendarBase: React.FC = ({ - value: externalValue, - min, - max, - type = 'Days', - eventList, - disabledList, - onChangeValue, - ...rest -}) => { - const [firstValue, secondValue] = useMemo(() => (Array.isArray(externalValue) ? externalValue : [externalValue]), [ - externalValue, - ]); - const value = secondValue || firstValue; - const [hoveredDay, setHoveredDay] = useState(); - const [prevType, setPrevType] = useState(type); - const [prevValue, setPrevValue] = useState(value); - const [outOfRangeKey, setOutOfRangeKey] = useState(0); - - const [state, dispatch] = useReducer(reducer, getInitialState(value, [5, 6], type)); - - const { date, calendarState, startYear, size } = state; - - const handlePrev = useCallback( - (withShift = false) => { - if (calendarState === CalendarState.Days) { - if (withShift) { - dispatch({ - type: ActionType.PREVIOUS_YEAR, - payload: { step: 1 }, - }); - - return; - } - - dispatch({ - type: ActionType.PREVIOUS_MONTH, - payload: { monthIndex: date.monthIndex, year: date.year }, - }); - - return; - } - - if (calendarState === CalendarState.Months) { - dispatch({ type: ActionType.PREVIOUS_YEAR, payload: { step: 1 } }); - - return; - } - - if (calendarState === CalendarState.Years) { - dispatch({ type: ActionType.PREVIOUS_START_YEAR, payload: { yearsCount: YEAR_RENDER_COUNT } }); - } - }, - [date, calendarState], - ); - - const handleNext = useCallback( - (withShift = false) => { - if (calendarState === CalendarState.Days) { - if (withShift) { - dispatch({ - type: ActionType.NEXT_YEAR, - payload: { step: 1 }, - }); - - return; - } - - dispatch({ - type: ActionType.NEXT_MONTH, - payload: { monthIndex: date.monthIndex, year: date.year }, - }); - - return; - } - - if (calendarState === CalendarState.Months) { - dispatch({ type: ActionType.NEXT_YEAR, payload: { step: 1 } }); - - return; - } - - if (calendarState === CalendarState.Years) { - dispatch({ type: ActionType.NEXT_START_YEAR, payload: { yearsCount: YEAR_RENDER_COUNT } }); - } - }, - [date, calendarState], - ); - - const [selectIndexes, onKeyDown, onSelectIndexes, outerRefs, isOutOfRange] = useKeyNavigation({ - size, - onNext: handleNext, - onPrev: handlePrev, - }); - - const handleOnChangeDay = useCallback( - (newDate: DateObject, coord: number[]) => { - const newDay = new Date(newDate.year, newDate.monthIndex, newDate.day); - onChangeValue(newDay); - - onSelectIndexes(coord); - }, - [onChangeValue, onSelectIndexes], - ); - - const handleOnChangeMonth = useCallback((monthIndex: number) => { - dispatch({ - type: ActionType.UPDATE_MONTH, - payload: { calendarState: CalendarState.Days, monthIndex, size: [5, 6] }, - }); - }, []); - - const handleOnChangeYear = useCallback((year: number) => { - dispatch({ - type: ActionType.UPDATE_YEAR, - payload: { calendarState: CalendarState.Months, year }, - }); - }, []); - - const handleUpdateCalendarState = useCallback((newCalendarState: CalendarStateType, newSize: [number, number]) => { - dispatch({ - type: ActionType.UPDATE_CALENDAR_STATE, - payload: { calendarState: newCalendarState, size: newSize }, - }); - }, []); - - if (value && prevValue && isValueUpdate(value, prevValue)) { - dispatch({ - type: ActionType.UPDATE_DATE, - payload: { value }, - }); - - setPrevValue(value); - } - - if (prevType !== type) { - dispatch({ - type: ActionType.UPDATE_CALENDAR_STATE, - payload: { calendarState: type }, - }); - - setPrevType(type); - } - - // Изменяем ключ каждый раз как пытаемся перейти на даты которые находятся за пределами min/max ограничений. - // Это необходимо для того чтобы screen-reader корректно озвучивал уведомление aria-live="assertive" - // о том что нет доступных дат - const handleKeyDown = useCallback( - (event: KeyboardEvent) => { - setOutOfRangeKey((previousState) => Number(!previousState)); - - onKeyDown(event); - }, - [onKeyDown], - ); - - return ( - - {isOutOfRange && ( - - Далее нет доступных дат. - - )} - - {calendarState === CalendarState.Days && ( - - )} - {calendarState === CalendarState.Months && ( - - )} - {calendarState === CalendarState.Years && ( - - )} - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarDayItem.tsx b/packages/plasma-hope/src/components/Calendar/CalendarDayItem.tsx deleted file mode 100644 index 4be9d281ed..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarDayItem.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import styled, { css } from 'styled-components'; -import { FocusProps, accent, tertiary, secondary, primary, surfaceLiquid02, addFocus } from '@salutejs/plasma-core'; -import { bodyS } from '@salutejs/plasma-typo'; - -import { flexCenter, selected } from './mixins'; -import type { DayProps, EventDay } from './types'; - -export interface CalendarDayItemProps extends DayProps, React.HTMLAttributes { - day: number | string; - year?: number; - monthIndex?: number; - sideInRange?: 'left' | 'right'; - eventList?: EventDay[]; - isFocused?: boolean; - disabledArrowKey?: string; - disabledMonths?: string; -} - -const StyledDay = styled.div<{ inRange?: boolean }>` - border-radius: 0.375rem; - align-items: center; - - ${flexCenter}; - - ${({ inRange }) => - inRange && - css` - &::before { - content: ''; - z-index: -1; - position: absolute; - width: 2.5rem; - height: 1.875rem; - background: ${surfaceLiquid02}; - } - `} -`; - -const setSide = (side: 'left' | 'right', isCurrent?: boolean, isSelected?: boolean) => { - switch (side) { - case 'left': - return `left: ${!isSelected && isCurrent ? '-1px' : '0'}`; - case 'right': - return `right: ${!isSelected && isCurrent ? '-1px' : '0'}`; - default: - return undefined; - } -}; - -const StyledDayRoot = styled.div` - ${bodyS}; - - position: relative; - box-sizing: border-box; - - min-width: 2.5rem; - min-height: 2rem; - - border-radius: 0.5rem; - - ${flexCenter}; - - ${({ isDayInCurrentMonth }) => css` - color: ${isDayInCurrentMonth ? primary : secondary}; - `} - - ${({ isDayInCurrentMonth, isDouble }) => css` - visibility: ${!isDayInCurrentMonth && isDouble ? 'hidden' : 'visible'}; - `} - - ${({ sideInRange, isCurrent, isSelected }) => - sideInRange && - css` - ${StyledDay}::before { - content: ''; - z-index: -1; - position: absolute; - width: 0.313rem; - height: 1.875rem; - background: ${surfaceLiquid02}; - ${setSide(sideInRange, isCurrent, isSelected)}; - } - `} - - ${({ dayOfWeek }) => - dayOfWeek && - css` - color: ${tertiary}; - `} - - ${({ isSelected, isCurrent, isHovered, dayOfWeek, $disabled }) => - !dayOfWeek && - !$disabled && - selected({ - StyledItem: StyledDay, - minWidth: 2.25, - minHeight: 1.75, - isSelected, - isCurrent, - isHovered, - })}; - - ${({ $disabled, isCurrent }) => - $disabled && - css` - cursor: not-allowed; - opacity: 0.4; - - ${addFocus({ - outlineRadius: '0.563rem', - outlineSize: '0.063rem', - outlineOffset: isCurrent ? '0.125rem' : '0.063rem', - })}; - `} -`; - -const StyledEvents = styled.div` - display: flex; - - position: absolute; - bottom: 0.25rem; -`; - -const StyledEvent = styled.div<{ color?: string }>` - margin: 0 0.063rem; - - width: 0.188rem; - height: 0.188rem; - border-radius: 50%; - - ${({ color = accent }) => css` - background-color: ${color}; - `} -`; - -/** - * Компонент дня в календаре. - */ -export const CalendarDayItem = memo( - forwardRef( - ( - { - isFocused, - dayOfWeek, - disabled, - isCurrent, - isSelected, - isDayInCurrentMonth, - isDouble, - inRange, - isHovered, - sideInRange, - eventList = [], - day, - monthIndex, - year, - onClick, - onMouseOver, - onFocus, - disabledArrowKey, - disabledMonths, - ...rest - }, - outerRef, - ) => { - return ( - - {day} - - {[eventList[0], eventList[1], eventList[2]].map( - (event, i) => event && , - )} - - - ); - }, - ), -); diff --git a/packages/plasma-hope/src/components/Calendar/CalendarDays.tsx b/packages/plasma-hope/src/components/Calendar/CalendarDays.tsx deleted file mode 100644 index 84e7f56aef..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarDays.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import React, { useCallback, useEffect, useRef } from 'react'; -import styled from 'styled-components'; - -import type { DateItem, DateObject, DisabledDay, EventDay, CalendarValueType } from './types'; -import { - canSelectDate, - FULL_DAY_NAMES, - getInRange, - getSideInRange, - isSameDay, - isSelectProcess, - ROW_STEP, - SHORT_DAY_NAMES, -} from './utils'; -import { useDays } from './hooks'; -import { flexCenter } from './mixins'; -import { CalendarDayItem } from './CalendarDayItem'; - -export interface CalendarDaysProps extends React.HTMLAttributes { - date: DateObject; - value: CalendarValueType; - min?: Date; - max?: Date; - eventList?: EventDay[]; - disabledList?: DisabledDay[]; - isDouble?: boolean; - isSecond?: boolean; - hoveredDay?: DateObject; - selectIndexes?: number[]; - outerRefs: React.MutableRefObject; - onHoverDay?: (date?: DateObject) => void; - onChangeDay: (date: DateObject, coord: number[]) => void; - onSetSelected?: (selected: number[]) => void; - onKeyDown?: (event: React.KeyboardEvent) => void; -} - -const StyledFlex = styled.div` - ${flexCenter}; -`; - -const StyledCalendarDays = styled.div` - outline: none; - - padding: 0.5rem 1rem 1.5rem; - box-sizing: border-box; -`; - -const StyledCalendarDaysHint = styled.span` - display: none; -`; - -/** - * Компонент дней в календаре. - */ -export const CalendarDays: React.FC = ({ - date: currentDate, - value, - eventList, - disabledList, - min, - max, - hoveredDay, - selectIndexes, - isDouble, - isSecond, - outerRefs, - onChangeDay, - onHoverDay, - onSetSelected, - onKeyDown, -}) => { - const [days, selected] = useDays(currentDate, value, eventList, disabledList, min, max); - const selectedRef = useRef(selected); - const onSetSelectedRef = useRef(onSetSelected); - - const offset = isSecond ? ROW_STEP : 0; - - const getSelectedDate = useCallback( - (event: React.MouseEvent) => { - const { day, monthIndex, year } = event.currentTarget.dataset; - - const selectedDate = { - day: Number(day), - monthIndex: Number(monthIndex), - year: Number(year), - }; - - if (!canSelectDate(selectedDate, value, disabledList)) { - return; - } - - return selectedDate; - }, - [disabledList, value], - ); - - const handleOnChangeDay = useCallback( - (i: number, j: number) => (event: React.MouseEvent) => { - const selectedDate = getSelectedDate(event); - - if (!selectedDate) { - return; - } - - onChangeDay(selectedDate, [i + offset, j]); - - if (isSelectProcess(value)) { - onHoverDay?.(undefined); - } - }, - [getSelectedDate, onChangeDay, offset, value, onHoverDay], - ); - - const handleOnHoverDay = useCallback( - (event: React.MouseEvent) => { - const selectedDate = getSelectedDate(event); - const isSelectedDone = Array.isArray(value) && value[0] && value[1]; - - if (!selectedDate || !Array.isArray(value) || isSelectedDone) { - return; - } - - onHoverDay?.(selectedDate); - }, - [getSelectedDate, onHoverDay, value], - ); - - const handleOnFocusDay = useCallback(() => { - // заглушка будет убрана при реализации доступности - }, []); - - const getRefs = useCallback( - (element: HTMLDivElement, isDayInCurrentMonth: boolean, i: number, j: number) => { - if (isDayInCurrentMonth) { - outerRefs.current[i + offset][j] = element; - } - }, - [offset, outerRefs], - ); - - useEffect(() => { - if (selectedRef.current) { - onSetSelectedRef.current?.(selectedRef.current); - } - }, []); - - return ( - - - Для навигации только по доступным датам удерживайте клавишу Shift. - - - {SHORT_DAY_NAMES.map((name) => ( - - ))} - - {days.map((day: DateItem[], i) => ( - - {day.map( - ( - { - date, - events, - disabled, - isSelected, - isCurrent, - isDayInCurrentMonth, - inRange, - isOutOfMinMaxRange = false, - disabledArrowKey, - disabledMonths, - }, - j, - ) => ( - getRefs(element, isDayInCurrentMonth, i, j)} - eventList={events} - disabled={disabled} - day={date.day} - year={date.year} - monthIndex={date.monthIndex} - isFocused={ - i + offset === selectIndexes?.[0] && j === selectIndexes?.[1] && !isOutOfMinMaxRange - } - isSelected={isSelected} - isCurrent={isCurrent} - isDayInCurrentMonth={isDayInCurrentMonth} - isDouble={isDouble} - isHovered={isSameDay(date, hoveredDay)} - inRange={getInRange(value, date, hoveredDay, inRange)} - sideInRange={getSideInRange(value, date, hoveredDay, isSelected)} - onClick={disabled ? undefined : handleOnChangeDay(i, j)} - onMouseOver={disabled ? undefined : handleOnHoverDay} - onFocus={handleOnFocusDay} - key={`StyledDay-${j}`} - role="gridcell" - disabledArrowKey={disabledArrowKey} - disabledMonths={disabledMonths} - /> - ), - )} - - ))} - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarDouble.tsx b/packages/plasma-hope/src/components/Calendar/CalendarDouble.tsx deleted file mode 100644 index 11843359fc..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarDouble.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import React, { useState, useCallback, useMemo } from 'react'; -import styled from 'styled-components'; -import { surfaceLiquid02 } from '@salutejs/plasma-core'; - -import type { DateObject, Calendar } from './types'; -import { getDateFromValue, getNextDate, getPrevDate, isValueUpdate } from './utils'; -import { CalendarDays } from './CalendarDays'; -import { CalendarHeader } from './CalendarHeader'; -import { useKeyNavigation } from './hooks'; - -export type CalendarDoubleProps = Calendar; - -const StyledCalendar = styled.div` - position: relative; - user-select: none; - z-index: 1; - - display: flex; - flex-direction: column; - - width: 39rem; - height: 19.5rem; -`; - -const StyledSeparator = styled.div` - background-color: ${surfaceLiquid02}; - min-width: 0.063rem; - margin: 0.5rem 0 1.5rem; -`; - -const StyledWrapper = styled.div` - display: flex; -`; - -/** - * Компонент двойного календаря. - */ -export const CalendarDouble: React.FC = ({ - value: externalValue, - min, - max, - eventList, - disabledList, - onChangeValue, - ...rest -}) => { - const [firstValue, secondValue] = useMemo(() => (Array.isArray(externalValue) ? externalValue : [externalValue]), [ - externalValue, - ]); - const value = secondValue || firstValue; - const [hoveredDay, setHoveredDay] = useState(); - - const [date, setDate] = useState(getDateFromValue(value)); - const [prevValue, setPrevValue] = useState(value); - const [doubleDate, setMonths] = useState(() => { - const nextDate = getDateFromValue(firstValue); - const [initialYear, initialMonth] = getNextDate(nextDate.year, nextDate.monthIndex); - - return { - monthIndex: [nextDate.monthIndex, initialMonth], - year: [nextDate.year, initialYear], - }; - }); - - const handleMonth = useCallback( - (getDate: (currentYear: number, currentMonth: number) => number[]) => { - const [newCurrentYear, newCurrentMonth] = getDate(doubleDate.year[0], doubleDate.monthIndex[0]); - const [newNextYear, newNextMonth] = getDate(doubleDate.year[1], doubleDate.monthIndex[1]); - - setMonths({ - monthIndex: [newCurrentMonth, newNextMonth], - year: [newCurrentYear, newNextYear], - }); - }, - [doubleDate], - ); - - const handlePrev = useCallback(() => { - handleMonth(getPrevDate); - }, [handleMonth]); - - const handleNext = useCallback(() => { - handleMonth(getNextDate); - }, [handleMonth]); - - const [selectIndexes, onKeyDown, onSelectIndexes, outerRefs] = useKeyNavigation({ - isDouble: true, - size: [11, 6], - onNext: handleNext, - onPrev: handlePrev, - }); - - const handleOnChangeDay = useCallback( - (newDate: DateObject, coord: number[]) => { - const newDay = new Date(newDate.year, newDate.monthIndex, newDate.day); - onChangeValue(newDay); - - onSelectIndexes(coord); - }, - [onChangeValue, onSelectIndexes], - ); - - const firstDate = useMemo( - () => ({ - day: date.day, - year: doubleDate.year[0], - monthIndex: doubleDate.monthIndex[0], - }), - [date, doubleDate], - ); - - const secondDate = useMemo( - () => ({ - day: date.day, - year: doubleDate.year[1], - monthIndex: doubleDate.monthIndex[1], - }), - [date, doubleDate], - ); - - if (value && prevValue && isValueUpdate(value, prevValue)) { - const newDate = getDateFromValue(value); - - const { year, monthIndex } = newDate; - - const { - monthIndex: [, prevMonthIndex], - year: [, prevYear], - } = doubleDate; - - if (prevMonthIndex !== monthIndex || prevYear !== year) { - const [nextYear, nextMonthIndex] = getNextDate(year, monthIndex); - - setDate(newDate); - - setMonths({ - monthIndex: [monthIndex, nextMonthIndex], - year: [year, nextYear], - }); - } - - setPrevValue(value); - } - - return ( - - - - - - - - - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarHeader.tsx b/packages/plasma-hope/src/components/Calendar/CalendarHeader.tsx deleted file mode 100644 index bd6432028f..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarHeader.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; -import { h4Bold } from '@salutejs/plasma-typo'; -import { IconDisclosureLeft, IconDisclosureRight } from '@salutejs/plasma-icons'; -import { primary } from '@salutejs/plasma-core'; - -import { CalendarState } from './types'; -import type { CalendarStateType, DateObject } from './types'; -import { MONTH_NAMES, YEAR_RENDER_COUNT, getCalendarType } from './utils'; -import { buttonFocus, flexCenter, flexSpaceBetween } from './mixins'; - -export interface CalendarHeaderProps extends React.HTMLAttributes { - firstDate: DateObject; - secondDate?: DateObject; - startYear?: number; - type?: CalendarStateType; - isDouble?: boolean; - onPrev: () => void; - onNext: () => void; - onUpdateCalendarState?: (newType: CalendarStateType, newSize: [number, number]) => void; -} - -const StyledCalendarHeader = styled.div` - ${h4Bold}; - - padding: 1rem 1.5rem 0; - - ${flexSpaceBetween}; -`; - -const StyledHeader = styled.button.attrs({ - type: 'button', -})` - ${h4Bold}; - - ${buttonFocus}; - - color: ${primary}; - cursor: pointer; - padding: 0.5rem 0; - - ${flexSpaceBetween}; -`; - -const StyledHeaderDouble = styled.h4` - ${h4Bold}; - - margin-top: 0; - margin-bottom: 0; - padding: 0.5rem 0; - flex: 1; - - ${flexCenter}; - - &:first-of-type { - margin-right: 3rem; - } - - &:last-of-type { - margin-left: 3rem; - } -`; - -const StyledArrows = styled.div` - padding: 0.5rem 0; - width: 5.5rem; - - ${flexSpaceBetween}; -`; - -const StyledArrow = styled.button.attrs({ - type: 'button', -})` - ${buttonFocus}; - - display: flex; - cursor: pointer; -`; - -const StyledNavigation = styled.div` - width: 100%; - - ${flexCenter}; -`; - -/** - * Компонент шапки календаря. - */ -export const CalendarHeader: React.FC = ({ - type = 'Days', - startYear = 0, - firstDate, - secondDate, - isDouble, - onPrev, - onNext, - onUpdateCalendarState, -}) => { - const handleCalendarState = useCallback(() => { - if (type === CalendarState.Days) { - onUpdateCalendarState?.(CalendarState.Months, [3, 2]); - } - - if (type === CalendarState.Months) { - onUpdateCalendarState?.(CalendarState.Years, [3, 2]); - } - }, [type, onUpdateCalendarState]); - - const getHeaderContent = useCallback( - (date?: DateObject) => { - if (!date) { - return ''; - } - - if (type === CalendarState.Days) { - return `${MONTH_NAMES[date.monthIndex]} ${date.year}`; - } - - if (type === CalendarState.Months) { - return `${date.year}`; - } - - if (type === CalendarState.Years) { - return `${startYear}—${startYear + YEAR_RENDER_COUNT - 1}`; - } - - return ''; - }, - [type, startYear], - ); - - const currentCalendarType = getCalendarType(isDouble ? CalendarState.Days : type); - - const PreviousButton = useMemo( - () => ( - onPrev()}> - - - ), - [currentCalendarType, onPrev], - ); - - const NextButton = useMemo( - () => ( - onNext()}> - - - ), - [currentCalendarType, onNext], - ); - - return ( - - {isDouble ? ( - - {PreviousButton} - {getHeaderContent(firstDate)} - {getHeaderContent(secondDate)} - {NextButton} - - ) : ( - <> - - {getHeaderContent(firstDate)} - - - {PreviousButton} - {NextButton} - - - )} - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarMonths.tsx b/packages/plasma-hope/src/components/Calendar/CalendarMonths.tsx deleted file mode 100644 index 5a0f3c71f6..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarMonths.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useCallback, useEffect, useRef } from 'react'; -import styled from 'styled-components'; -import { FocusProps } from '@salutejs/plasma-core'; -import { bodyS } from '@salutejs/plasma-typo'; - -import type { DateObject, MonthsProps } from './types'; -import { useMonths } from './hooks'; -import { flexCenter, selected as selectedMixin } from './mixins'; - -export interface CalendarMonthsProps extends React.HTMLAttributes { - date: DateObject; - selectIndexes?: number[]; - outerRefs: React.MutableRefObject; - onChangeMonth: (month: number) => void; - onSetRefs?: (refs: HTMLDivElement[][]) => void; - onSetSelected?: (selected: number[]) => void; - onKeyDown?: (event: React.KeyboardEvent) => void; -} - -const StyledCalendarMonths = styled.div` - padding: 0.5rem 1.5rem 1.5rem; - box-sizing: border-box; -`; - -const StyledFlex = styled.div` - ${flexCenter}; -`; - -const StyledMonth = styled.div` - border-radius: 0.438rem; - - ${flexCenter}; -`; - -const StyledMonthRoot = styled.div` - ${bodyS}; - - position: relative; - box-sizing: border-box; - - min-height: 3.5rem; - - border-radius: 0.5rem; - - flex: 1; - - ${flexCenter}; - - ${({ isSelected, isCurrent }) => - selectedMixin({ - StyledItem: StyledMonth, - minWidth: 5.25, - minHeight: 3.25, - isSelected, - isCurrent, - })}; -`; - -/** - * Компонент месяцев в календаре. - */ -export const CalendarMonths: React.FC = ({ - date: currentDate, - selectIndexes, - outerRefs, - onChangeMonth, - onSetSelected, - onKeyDown, -}) => { - const [months, selected] = useMonths(currentDate); - const selectedRef = useRef(selected); - const onSetSelectedRef = useRef(onSetSelected); - - const handleOnChangeMonth = useCallback( - (event: React.MouseEvent) => { - /** - * нужно вызвать stopImmediatePropagation для случаев, когда - * обработчик события onClick навешивается снаружи. - * Как, например, в компоненте Popup - */ - event.nativeEvent.stopImmediatePropagation(); - - const { monthIndex } = event.currentTarget.dataset; - onChangeMonth(Number(monthIndex)); - }, - [onChangeMonth], - ); - - const getRefs = useCallback( - (element: HTMLDivElement, i: number, j: number) => { - outerRefs.current[i][j] = element; - }, - [outerRefs], - ); - - useEffect(() => { - if (selectedRef.current) { - onSetSelectedRef.current?.(selectedRef.current); - } - }, []); - - return ( - - {months.map((month, i) => ( - - {month.map(({ monthName, monthIndex, isSelected, isCurrent, monthFullName }, j) => ( - getRefs(element, i, j)} - tabIndex={i === selectIndexes?.[0] && j === selectIndexes?.[1] ? 0 : -1} - isCurrent={isCurrent} - isSelected={isSelected} - onClick={handleOnChangeMonth} - data-month-index={monthIndex} - aria-selected={isSelected} - role="gridcell" - key={`StyledMonth-${i}-${j}`} - aria-label={monthFullName} - > - {monthName} - - ))} - - ))} - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/CalendarYears.tsx b/packages/plasma-hope/src/components/Calendar/CalendarYears.tsx deleted file mode 100644 index 79426e2bf7..0000000000 --- a/packages/plasma-hope/src/components/Calendar/CalendarYears.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useCallback, useEffect, useRef } from 'react'; -import styled from 'styled-components'; -import { FocusProps } from '@salutejs/plasma-core'; -import { bodyS } from '@salutejs/plasma-typo'; - -import type { DateObject, YearsProps } from './types'; -import { useYears } from './hooks'; -import { flexCenter, selected as selectedMixin } from './mixins'; - -export interface CalendarYearsProps extends React.HTMLAttributes { - date: DateObject; - startYear: number; - selectIndexes?: number[]; - outerRefs: React.MutableRefObject; - onChangeYear: (year: number) => void; - onSetRefs?: (refs: HTMLDivElement[][]) => void; - onSetSelected?: (selected: number[]) => void; - onKeyDown?: (event: React.KeyboardEvent) => void; -} - -const StyledCalendarYears = styled.div` - padding: 0.5rem 1.5rem 1.5rem; - box-sizing: border-box; -`; - -const StyledFlex = styled.div` - ${flexCenter}; -`; - -const StyledYear = styled.div` - border-radius: 0.438rem; - - ${flexCenter}; -`; - -const StyledYearRoot = styled.div` - ${bodyS}; - - position: relative; - box-sizing: border-box; - - min-height: 3.5rem; - - border-radius: 0.5rem; - - flex: 1; - - ${flexCenter}; - - ${({ isSelected, isCurrent }) => - selectedMixin({ - StyledItem: StyledYear, - minWidth: 5.25, - minHeight: 3.25, - isSelected, - isCurrent, - })}; -`; - -/** - * Компонент годов в календаре. - */ -export const CalendarYears: React.FC = ({ - date: currentDate, - startYear, - selectIndexes, - outerRefs, - onChangeYear, - onSetSelected, - onKeyDown, -}) => { - const [years, selected] = useYears(currentDate, startYear); - const selectedRef = useRef(selected); - const onSetSelectedRef = useRef(onSetSelected); - - const handleOnChangeYear = useCallback( - (event: React.MouseEvent) => { - /** - * нужно вызвать stopImmediatePropagation для случаев, когда - * обработчик события onClick навешивается снаружи. - * Как, например, в компоненте Popup - */ - event.nativeEvent.stopImmediatePropagation(); - - const { year } = event.currentTarget.dataset; - onChangeYear(Number(year)); - }, - [onChangeYear], - ); - - const getRefs = useCallback( - (element: HTMLDivElement, i: number, j: number) => { - outerRefs.current[i][j] = element; - }, - [outerRefs], - ); - - useEffect(() => { - if (selectedRef.current) { - onSetSelectedRef.current?.(selectedRef.current); - } - }, []); - - return ( - - {years.map((year, i) => ( - - {year.map(({ yearValue, isSelected, isCurrent }, j) => ( - getRefs(element, i, j)} - tabIndex={i === selectIndexes?.[0] && j === selectIndexes?.[1] ? 0 : -1} - isCurrent={isCurrent} - isSelected={isSelected} - onClick={handleOnChangeYear} - data-year={yearValue} - aria-selected={isSelected} - role="gridcell" - key={`StyledYear-${i}-${j}`} - aria-label={String(yearValue)} - > - {yearValue} - - ))} - - ))} - - ); -}; diff --git a/packages/plasma-hope/src/components/Calendar/hooks/index.ts b/packages/plasma-hope/src/components/Calendar/hooks/index.ts deleted file mode 100644 index 2d1e8aa257..0000000000 --- a/packages/plasma-hope/src/components/Calendar/hooks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { useDays } from './useDays'; -export { useMonths } from './useMonths'; -export { useYears } from './useYears'; -export { useKeyNavigation } from './useKeyNavigation'; diff --git a/packages/plasma-hope/src/components/Calendar/hooks/useDays.ts b/packages/plasma-hope/src/components/Calendar/hooks/useDays.ts deleted file mode 100644 index 63caa2717f..0000000000 --- a/packages/plasma-hope/src/components/Calendar/hooks/useDays.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { useMemo } from 'react'; - -import { CalendarValueType, DateItem, DateObject, DisabledDay, EventDay } from '../types'; -import { - getDateFromValue, - getDaysInMonth, - getMatrix, - getNextDate, - getOffsetDayInWeek, - getPrevDate, - IsCurrentDay, - isDayInRage, - isSelectedDay, -} from '../utils'; - -/** - * Метод возвращающий массив дней в предыдущем месяце. - */ -const getDaysInPrevMonth = (date: DateObject, offsetDayInWeek: number, value: CalendarValueType) => { - const [prevYear, prevMonth] = getPrevDate(date.year, date.monthIndex); - const daysInPrevMonth = getDaysInMonth(prevMonth, prevYear); - - return Array.from(Array(offsetDayInWeek), (_, i) => ({ - isCurrent: false, - isSelected: false, - isDayInCurrentMonth: false, - inRange: Array.isArray(value) - ? isDayInRage(prevYear, prevMonth, daysInPrevMonth - (offsetDayInWeek - i) + 1, value) - : false, - date: { - day: daysInPrevMonth - (offsetDayInWeek - i) + 1, - monthIndex: prevMonth, - year: prevYear, - }, - })); -}; - -/** - * Метод возвращающий массив дней в текущем месяце. - */ -const getDaysInCurrentMonth = (date: DateObject, daysInMonth: number, value: CalendarValueType) => { - return Array.from(Array(daysInMonth), (_, i) => ({ - isCurrent: IsCurrentDay(date, i + 1), - isSelected: Array.isArray(value) - ? Boolean(value.find((v) => isSelectedDay(date, i + 1, v))) - : isSelectedDay(date, i + 1, value), - isDayInCurrentMonth: true, - inRange: Array.isArray(value) ? isDayInRage(date.year, date.monthIndex, i + 1, value) : false, - date: { - day: i + 1, - monthIndex: date.monthIndex, - year: date.year, - }, - })); -}; - -/** - * Метод возвращающий массив дней в следующем месяце. - */ -const getDaysInNextMonth = ( - date: DateObject, - daysInMonth: number, - offsetDayInWeek: number, - value: CalendarValueType, -) => { - const [nextYear, nextMonthIndex] = getNextDate(date.year, date.monthIndex); - const visibleDayCount = 42; - const restDaysInCalendar = visibleDayCount - (daysInMonth + offsetDayInWeek); - - return Array.from(Array(restDaysInCalendar), (_, i) => ({ - isCurrent: false, - isSelected: false, - isDayInCurrentMonth: false, - inRange: Array.isArray(value) ? isDayInRage(nextYear, nextMonthIndex, i + 1, value) : false, - date: { - day: i + 1, - monthIndex: nextMonthIndex, - year: nextYear, - }, - })); -}; - -const isDisabledArrowLeft = (date: Date, min?: Date) => { - const currentDate = new Date(date); - - currentDate.setDate(currentDate.getDate() - 1); - - return (min && min >= currentDate) || (min && min >= date); -}; - -const isDisabledArrowRight = (date: Date, max?: Date) => { - const currentDate = new Date(date); - - currentDate.setDate(currentDate.getDate() + 1); - - return (max && max <= currentDate) || (max && max <= date); -}; - -const isDisabledArrowUp = (date: Date, min?: Date) => { - const currentDate = new Date(date); - - currentDate.setDate(date.getDate() - 7); - - return min && min >= currentDate; -}; - -const isDisabledArrowDown = (date: Date, max?: Date) => { - const currentDate = new Date(date); - - currentDate.setDate(date.getDate() + 7); - - return max && max <= currentDate; -}; - -const isDisabledNextMonth = ({ year, monthIndex, day }: DateObject, max?: Date) => { - if (!max) { - return false; - } - - const currentDate = new Date(year, monthIndex, day); - - currentDate.setDate(currentDate.getDate() + 1); - - let isOut = true; - - while (isOut && currentDate <= max) { - isOut = max <= currentDate; - - currentDate.setDate(currentDate.getDate() + 1); - } - - return isOut; -}; - -const isDisabledPreviousMonth = ({ year, monthIndex, day }: DateObject, min?: Date) => { - if (!min) { - return false; - } - - const currentDate = new Date(year, monthIndex, day); - - currentDate.setDate(currentDate.getDate() - 1); - - let isOut = true; - - while (isOut && currentDate >= min) { - isOut = min >= currentDate; - - currentDate.setDate(currentDate.getDate() - 1); - } - - return isOut; -}; - -const getDisabledArrowKey = (currentDate: Date, min?: Date, max?: Date) => { - const disabledArrowKey = []; - - if (isDisabledArrowLeft(currentDate, min)) { - disabledArrowKey.push('left'); - } - - if (isDisabledArrowRight(currentDate, max)) { - disabledArrowKey.push('right'); - } - - if (isDisabledArrowDown(currentDate, max)) { - disabledArrowKey.push('down'); - } - - if (isDisabledArrowUp(currentDate, min)) { - disabledArrowKey.push('up'); - } - - return disabledArrowKey.join(','); -}; - -const getDisabledMonths = (list: DateObject[], min?: Date, max?: Date) => { - const disabledMonth = []; - - if (isDisabledPreviousMonth(list[0], min)) { - disabledMonth.push('previous'); - } - - if (isDisabledNextMonth(list[list.length - 1], max)) { - disabledMonth.push('next'); - } - - return disabledMonth.join(','); -}; - -/** - * Метод для получения набора неповторяющихся дат. - */ -const getPropsMap = (props: T[]) => - props.reduce((acc, prop) => { - const { year, monthIndex, day } = getDateFromValue(prop.date); - - const key = `${year}-${monthIndex}-${day}`; - - const propList = acc.get(key) || []; - propList.push(prop); - - return acc.set(key, propList); - }, new Map()); - -/** - * Метод модифицирующий дни (добавляющий свойства events и disabled). - */ -const getDaysWithModifications = ( - days: DateItem[], - eventList: EventDay[] = [], - disabledList: DisabledDay[] = [], - min?: Date, - max?: Date, -) => { - const eventsMap = getPropsMap(eventList); - const disabledDaysMap = getPropsMap(disabledList); - - const daysList = days.filter(({ isDayInCurrentMonth }) => isDayInCurrentMonth).map(({ date }) => date); - - const disabledMonths = getDisabledMonths(daysList, min, max); - - return days.map((dayItem) => { - const { date } = dayItem; - const { year, monthIndex, day } = date; - - const keyDay = `${year}-${monthIndex}-${day}`; - const currentDate = new Date(year, monthIndex, day); - - const isOutOfMinMaxRange = (min && min >= currentDate) || (max && max <= currentDate); - - dayItem.events = eventsMap.get(keyDay); - dayItem.disabled = disabledDaysMap.has(keyDay) || isOutOfMinMaxRange; - - dayItem.isOutOfMinMaxRange = isOutOfMinMaxRange; - dayItem.disabledArrowKey = getDisabledArrowKey(currentDate, min, max); - dayItem.disabledMonths = disabledMonths; - - return dayItem; - }); -}; - -/** - * Хук для получения списка дней. - */ -export const useDays = ( - date: DateObject, - value: CalendarValueType, - eventList?: EventDay[], - disabledList?: DisabledDay[], - min?: Date, - max?: Date, -) => - useMemo(() => { - const { monthIndex, year } = date; - const daysInMonth = getDaysInMonth(monthIndex, year); - const offsetDayInWeek = getOffsetDayInWeek(monthIndex, year); - - const days = [ - ...getDaysInPrevMonth(date, offsetDayInWeek, value), - ...getDaysInCurrentMonth(date, daysInMonth, value), - ...getDaysInNextMonth(date, daysInMonth, offsetDayInWeek, value), - ]; - - if (eventList?.length || disabledList?.length || max || min) { - const modifiedDays = getDaysWithModifications(days, eventList, disabledList, min, max); - return getMatrix(modifiedDays); - } - - return getMatrix(days); - }, [date, value, eventList, disabledList, max, min]); diff --git a/packages/plasma-hope/src/components/Calendar/hooks/useKeyNavigation.ts b/packages/plasma-hope/src/components/Calendar/hooks/useKeyNavigation.ts deleted file mode 100644 index 9460f79831..0000000000 --- a/packages/plasma-hope/src/components/Calendar/hooks/useKeyNavigation.ts +++ /dev/null @@ -1,680 +0,0 @@ -import React, { KeyboardEvent, useCallback, useLayoutEffect, useRef, useState } from 'react'; - -import { DaysMetaDescription, KeyboardArrowKey, Keys, UseKeyNavigationProps } from '../types'; -import { ROW_STEP } from '../utils'; - -/** - * Метод для получения стороны двойного календаря. - */ -const getDoubleCalendarSide = (currentIndexWeek: number) => { - if (currentIndexWeek >= 0 && currentIndexWeek < ROW_STEP) { - return 'first'; - } - - if (currentIndexWeek >= ROW_STEP && currentIndexWeek < ROW_STEP * 2) { - return 'second'; - } - - return ''; -}; - -const isOutOfBound = ([rowIndex, columnIndex]: number[], rowSize: number, columnSize: number) => - columnIndex === -1 || columnIndex === columnSize + 1 || rowIndex === -1 || rowIndex === rowSize + 1; - -const isVisible = (refs: React.MutableRefObject, row: number, column: number) => - refs.current?.[row]?.[column]; - -const isAriaDisableItem = (item: HTMLDivElement) => item?.getAttribute('aria-disabled') === 'true'; - -const hasDisabledArrowKey = ({ - refs, - payload, - key, -}: { - refs: React.MutableRefObject; - payload: number[]; - key: KeyboardArrowKey; -}) => { - const [previousRowIndex, previousColumnIndex] = payload; - - const disabledArrowKey = refs.current?.[previousRowIndex]?.[previousColumnIndex]?.dataset.disabledArrowKey; - - return Boolean(disabledArrowKey?.includes(key)); -}; - -const hasDisabledMonths = ({ item, key }: { item: HTMLDivElement; key: 'previous' | 'next' }) => { - return item?.dataset?.disabledMonths ? item?.dataset?.disabledMonths.includes(key) : false; -}; - -const getNextCorrectPosition = ({ - refs, - rowSize, - newRowIndex, - newColumnIndex, - columnSize, - minColumnIndex, - defaultState = [], -}: DaysMetaDescription): number[] => { - let item = refs.current?.[newRowIndex]?.[newColumnIndex]; - - while (isAriaDisableItem(item) && newColumnIndex <= columnSize) { - newColumnIndex++; - - if (newColumnIndex > columnSize && newRowIndex < rowSize) { - newRowIndex++; - newColumnIndex = minColumnIndex; - } - - item = refs.current?.[newRowIndex]?.[newColumnIndex]; - - if (item) { - const isDisabledArrowDown = hasDisabledArrowKey({ - refs, - payload: [newRowIndex, newColumnIndex], - key: 'down', - }); - - const isDisabledArrowRight = hasDisabledArrowKey({ - refs, - payload: [newRowIndex, newColumnIndex], - key: 'right', - }); - - if (isDisabledArrowDown || isDisabledArrowRight) { - return defaultState; - } - } - } - - return [newRowIndex, newColumnIndex]; -}; - -const getPreviousCorrectPosition = ({ - refs, - rowSize, - newRowIndex, - newColumnIndex, - columnSize, - minColumnIndex, - defaultState = [], -}: DaysMetaDescription): number[] => { - let item = refs.current?.[newRowIndex]?.[newColumnIndex]; - - while (isAriaDisableItem(item) && newColumnIndex >= minColumnIndex) { - newColumnIndex--; - - if (newColumnIndex < minColumnIndex && newRowIndex <= rowSize) { - newRowIndex--; - newColumnIndex = columnSize; - } - - item = refs.current?.[newRowIndex]?.[newColumnIndex]; - - if (item && item.dataset.day === '1') { - const isDisabledArrowUp = hasDisabledArrowKey({ - refs, - payload: [newRowIndex, newColumnIndex], - key: 'up', - }); - - const isDisabledArrowLeft = hasDisabledArrowKey({ - refs, - payload: [newRowIndex, newColumnIndex], - key: 'left', - }); - - if (isDisabledArrowUp || isDisabledArrowLeft) { - return defaultState; - } - } - } - - return [newRowIndex, newColumnIndex]; -}; - -/** - * Метод для нахождения стартового индекса дня в следующем/предыдущем месяце - */ -function getCorrectColumnIndex({ - refs, - rowSize, - isNext, -}: { - refs: HTMLDivElement[][]; - rowSize: number; - isNext: boolean; -}): number { - if (isNext) { - let index = refs[rowSize].filter(Boolean).length; - - if (!index) { - index = refs[rowSize - 1].filter(Boolean).length; - } - - return index; - } - - // Смещение влево - начинаем поиск первого не-nullable элемента в первой строке - return refs[0].findIndex((item) => item) - 1; -} - -const getCorrectIndexes = ( - refs: React.MutableRefObject, - [rowIndex, columnIndex]: number[], - rowSize: number, - columnSize: number, - withShift: boolean, - defaultState: number[], -) => { - let newRowIndex = rowIndex; - let newColumnIndex = columnIndex; - - const minRowIndex = newRowIndex + 1; - const maxRowIndex = newRowIndex - 1; - - const minColumnIndex = 0; - const maxColumnIndex = columnSize; - - if (newColumnIndex === minColumnIndex - 1) { - newColumnIndex += 1; - - while (newColumnIndex < maxColumnIndex && !isVisible(refs, newRowIndex, newColumnIndex)) { - newColumnIndex++; - } - } - - if (newColumnIndex === columnSize + 1) { - newColumnIndex -= 1; - - while (newColumnIndex > minColumnIndex && !isVisible(refs, newRowIndex, newColumnIndex)) { - newColumnIndex--; - } - } - - if (newRowIndex === minColumnIndex - 1) { - newRowIndex = ROW_STEP - 1; - - while (newRowIndex > minRowIndex && !isVisible(refs, newRowIndex, newColumnIndex)) { - newRowIndex--; - } - } - - if (newRowIndex === rowSize + 1) { - newRowIndex = rowSize + 1 - ROW_STEP; - - while (newRowIndex <= maxRowIndex && !isVisible(refs, newRowIndex, newColumnIndex)) { - newRowIndex++; - } - } - - // INFO: Логика для получения правильной позиции дня, когда переключились на другой месяц - // INFO: и ставим указатель на первый доступный день. Только при зажатой клавиши Shift. - if (isAriaDisableItem(refs.current?.[newRowIndex]?.[newColumnIndex]) && withShift) { - const isNext = rowIndex === rowSize + 1; - const isPrevious = rowIndex === minColumnIndex - 1; - - const state = { refs, rowSize, newColumnIndex, minColumnIndex, columnSize, newRowIndex, defaultState }; - - if (isNext) { - return getNextCorrectPosition(state); - } - - if (isPrevious) { - return getPreviousCorrectPosition(state); - } - } - - return [newRowIndex, newColumnIndex]; -}; - -/** - * Хук для осуществления возможности клавиатурной навигации по матрице. - */ -export const useKeyNavigation = ({ isDouble = false, size, onPrev, onNext }: UseKeyNavigationProps) => { - const [rowSize, columnSize] = size; - const [selectIndexes, setSelectIndexes] = useState([0, 0]); - const [isOutOfMinMaxRange, setIsOutOfMinMaxRange] = useState(false); - - const withShiftState = useRef(false); - const currentIndexes = useRef([0, 0]); - - const outerRefs = useRef( - Array(rowSize + 1) - .fill(0) - .map(() => Array(columnSize + 1)), - ); - - useLayoutEffect(() => { - if (!isOutOfBound(selectIndexes, rowSize, columnSize)) { - return; - } - - if (withShiftState.current) { - const isNext = selectIndexes[0] === rowSize + 1; - - let refs = outerRefs.current; - - const isSecond = isDouble && isNext; - const isFirst = isDouble && !isNext; - - // Определяем какую часть сдвоенного календаря взять - if (isFirst) { - refs = outerRefs.current.slice(0, 5); - } else if (isSecond) { - refs = outerRefs.current.slice(5, 12); - } - - const refsList = refs?.flatMap((items) => items.filter(Boolean)); - - // Если в месяце нет хотя бы одной none-disabled даты значит весь месяц выключен. - const hasSomeEnabledDay = refsList.some((item) => !isAriaDisableItem(item)); - - const isDisabledNextMonth = hasDisabledMonths({ item: refsList[refsList.length - 1], key: 'next' }); - const isDisabledPreviousMonth = hasDisabledMonths({ item: refsList[0], key: 'previous' }); - - // Если следующий/предыдущий месяц находится за границами мин/макс, то переходить на него - // с текущего выключенного, нет смысла. - if (!hasSomeEnabledDay && (isDisabledNextMonth || isDisabledPreviousMonth)) { - const inverseHandle = isNext ? onPrev : onNext; - - inverseHandle(); - - setSelectIndexes(currentIndexes.current); - - // Если индексы равны, значит мы остались на текущей дате по причине, того - // что в следующем(-их)/предыдущем(-их) месяце нет доступных дат - setIsOutOfMinMaxRange(true); - - return; - } - - if (!hasSomeEnabledDay) { - const handle = isNext ? onNext : onPrev; - - const startColumnIndex = getCorrectColumnIndex({ - refs, - rowSize: isDouble ? refs.length - 1 : rowSize, - isNext, - }); - - handle(); - - setSelectIndexes([selectIndexes[0], startColumnIndex]); - - return; - } - } - - const [newRowIndex, newColumnIndex] = getCorrectIndexes( - outerRefs, - selectIndexes, - rowSize, - columnSize, - withShiftState.current, - currentIndexes.current, - ); - - /** - * Изменение состояния необходимо сделать здесь, т.к. - * требуется дождаться обновление DOM и outerRefs - */ - setSelectIndexes([newRowIndex, newColumnIndex]); - }, [onPrev, onNext, selectIndexes, rowSize, columnSize, withShiftState, currentIndexes, isDouble]); - - useLayoutEffect(() => { - const [rowIndex, columnIndex] = selectIndexes; - - const item = outerRefs?.current?.[rowIndex]?.[columnIndex]; - - if (item) { - item.focus(); - } - }, [selectIndexes]); - - const onKeyDown = useCallback( - (event: KeyboardEvent) => { - setIsOutOfMinMaxRange(false); - - const { keyCode, shiftKey: withShift } = event; - - const [currentRowIndex, currentColumnIndex] = selectIndexes; - - let newRowIndex = currentRowIndex; - let newColumnIndex = currentColumnIndex; - - const minColumnIndex = 0; - const minRowIndex = 0; - - const prevRowIndex = currentRowIndex - 1; - const nextRowIndex = currentRowIndex + 1; - - const prevColumnIndex = currentColumnIndex - 1; - const nextColumnIndex = currentColumnIndex + 1; - - currentIndexes.current = [currentRowIndex, currentColumnIndex]; - - const positionState = { - refs: outerRefs, - rowSize, - columnSize, - minColumnIndex, - defaultState: [currentRowIndex, currentColumnIndex], - }; - - switch (keyCode) { - case Keys.pageUp: { - const isDisabledPreviousMonth = hasDisabledMonths({ - item: outerRefs.current[currentRowIndex][currentColumnIndex], - key: 'previous', - }); - - if (isDisabledPreviousMonth) { - setIsOutOfMinMaxRange(true); - - break; - } - - onPrev(withShift); - - break; - } - case Keys.pageDown: { - const isDisabledNextMonth = hasDisabledMonths({ - item: outerRefs.current[currentRowIndex][currentColumnIndex], - key: 'next', - }); - - if (isDisabledNextMonth) { - setIsOutOfMinMaxRange(true); - - break; - } - - onNext(withShift); - - break; - } - case Keys.home: { - newColumnIndex = minColumnIndex; - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - newColumnIndex = minColumnIndex - 1; - - break; - } - case Keys.end: { - newColumnIndex = columnSize; - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - newColumnIndex = columnSize + 1; - - break; - } - - case Keys.left: { - newRowIndex = prevColumnIndex < minColumnIndex ? prevRowIndex : currentRowIndex; - newColumnIndex = prevColumnIndex < minColumnIndex ? columnSize : prevColumnIndex; - - withShiftState.current = withShift; - - const isCurrentDateDisabledArrowLeft = hasDisabledArrowKey({ - refs: outerRefs, - payload: [currentRowIndex, currentColumnIndex], - key: 'left', - }); - - setIsOutOfMinMaxRange(isCurrentDateDisabledArrowLeft); - - if (isCurrentDateDisabledArrowLeft) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - // INFO: Для навигации только по доступным дням - if (withShift) { - const [rowIndex, columnIndex] = getPreviousCorrectPosition({ - ...positionState, - newRowIndex, - newColumnIndex, - }); - - // Если индексы равны, значит мы остались на текущей дате по причине, того - // что в следующем(-их)/предыдущем(-их) месяце нет доступных дат - setIsOutOfMinMaxRange(rowIndex === currentRowIndex && currentColumnIndex === columnIndex); - - newRowIndex = rowIndex; - newColumnIndex = columnIndex; - } - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - if (!isDouble || getDoubleCalendarSide(currentRowIndex) === 'first') { - if (isCurrentDateDisabledArrowLeft) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - onPrev(); - } - - newRowIndex = minRowIndex - 1; - - break; - } - case Keys.up: { - newRowIndex = prevRowIndex < minRowIndex ? rowSize : prevRowIndex; - - withShiftState.current = withShift; - - const isCurrentDateDisabledArrowUp = hasDisabledArrowKey({ - refs: outerRefs, - payload: [currentRowIndex, currentColumnIndex], - key: 'up', - }); - - setIsOutOfMinMaxRange(isCurrentDateDisabledArrowUp); - - if (isCurrentDateDisabledArrowUp) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - if (withShift) { - const item = outerRefs.current[newRowIndex][newColumnIndex]; - - const isNextDateDisabledArrowUp = - !!item && - hasDisabledArrowKey({ - refs: outerRefs, - payload: [newRowIndex, newColumnIndex], - key: 'up', - }); - - const [rowIndex, columnIndex] = getPreviousCorrectPosition({ - ...positionState, - newRowIndex, - newColumnIndex, - }); - - // Если индексы равны, значит мы остались на текущей дате по причине, того - // что в следующем(-их)/предыдущем(-их) месяце нет доступных дат - const haseCurrentPosition = rowIndex === currentRowIndex && currentColumnIndex === columnIndex; - - setIsOutOfMinMaxRange(isNextDateDisabledArrowUp || haseCurrentPosition); - - newRowIndex = rowIndex; - newColumnIndex = columnIndex; - } - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - if (!isDouble || getDoubleCalendarSide(currentRowIndex) === 'first') { - if (isCurrentDateDisabledArrowUp) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - onPrev(); - } - - newRowIndex = minRowIndex - 1; - - break; - } - - case Keys.right: { - newRowIndex = nextColumnIndex > columnSize ? nextRowIndex : currentRowIndex; - newColumnIndex = nextColumnIndex > columnSize ? minColumnIndex : nextColumnIndex; - - withShiftState.current = withShift; - - const isCurrentDateDisabledArrowRight = hasDisabledArrowKey({ - refs: outerRefs, - payload: [currentRowIndex, currentColumnIndex], - key: 'right', - }); - - setIsOutOfMinMaxRange(isCurrentDateDisabledArrowRight); - - if (isCurrentDateDisabledArrowRight) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - if (withShift) { - const [rowIndex, columnIndex] = getNextCorrectPosition({ - ...positionState, - newRowIndex, - newColumnIndex, - }); - - // Если индексы равны, значит мы остались на текущей дате по причине, того - // что в следующем(-их)/предыдущем(-их) месяце нет доступных дат - setIsOutOfMinMaxRange(rowIndex === currentRowIndex && currentColumnIndex === columnIndex); - - newRowIndex = rowIndex; - newColumnIndex = columnIndex; - } - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - if (!isDouble || getDoubleCalendarSide(currentRowIndex) === 'second') { - if (isCurrentDateDisabledArrowRight) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - onNext(); - } - - newRowIndex = rowSize + 1; - - break; - } - case Keys.down: { - newRowIndex = nextRowIndex > rowSize ? minRowIndex : nextRowIndex; - - withShiftState.current = withShift; - - const isCurrentDateDisabledArrowDown = hasDisabledArrowKey({ - refs: outerRefs, - payload: [currentRowIndex, currentColumnIndex], - key: 'down', - }); - - setIsOutOfMinMaxRange(isCurrentDateDisabledArrowDown); - - if (isCurrentDateDisabledArrowDown) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - if (withShift) { - const item = outerRefs.current[newRowIndex][newColumnIndex]; - - const isNextDateDisabledArrowUp = - !!item && - hasDisabledArrowKey({ - refs: outerRefs, - payload: [newRowIndex, newColumnIndex], - key: 'down', - }); - - const [rowIndex, columnIndex] = getNextCorrectPosition({ - ...positionState, - newRowIndex, - newColumnIndex, - }); - - // Если индексы равны, значит мы остались на текущей дате по причине, того - // что в следующем(-их)/предыдущем(-их) месяце нет доступных дат - const haseCurrentPosition = rowIndex === currentRowIndex && currentColumnIndex === columnIndex; - - setIsOutOfMinMaxRange(isNextDateDisabledArrowUp || haseCurrentPosition); - - newRowIndex = rowIndex; - newColumnIndex = columnIndex; - } - - if (isVisible(outerRefs, newRowIndex, newColumnIndex)) { - break; - } - - if (!isDouble || getDoubleCalendarSide(currentRowIndex) === 'second') { - if (isCurrentDateDisabledArrowDown) { - newRowIndex = currentRowIndex; - newColumnIndex = currentColumnIndex; - - break; - } - - onNext(); - } - - newRowIndex = rowSize + 1; - - break; - } - case Keys.enter: - case Keys.space: { - outerRefs.current?.[newRowIndex]?.[newColumnIndex].click(); - break; - } - - default: - return; - } - - setSelectIndexes([newRowIndex, newColumnIndex]); - }, - [selectIndexes, outerRefs, rowSize, columnSize, onNext, onPrev, isDouble], - ); - - return [selectIndexes, onKeyDown, setSelectIndexes, outerRefs, isOutOfMinMaxRange] as const; -}; diff --git a/packages/plasma-hope/src/components/Calendar/hooks/useMonths.ts b/packages/plasma-hope/src/components/Calendar/hooks/useMonths.ts deleted file mode 100644 index a065665dd9..0000000000 --- a/packages/plasma-hope/src/components/Calendar/hooks/useMonths.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMemo } from 'react'; - -import { DateObject, MonthsItem } from '../types'; -import { SHORT_MONTH_NAME, isSelectedMonth, isCurrentMonth, getMatrix, MONTH_NAMES } from '../utils'; - -/** - * Хук для получения списка месяцев. - */ -export const useMonths = (date: DateObject) => - useMemo(() => { - const result = SHORT_MONTH_NAME.map((monthName, monthIndex) => ({ - monthName, - monthIndex, - isCurrent: isCurrentMonth(date, monthIndex), - isSelected: isSelectedMonth(date, monthIndex), - monthFullName: MONTH_NAMES[monthIndex], - })); - - return getMatrix(result, 3); - }, [date]); diff --git a/packages/plasma-hope/src/components/Calendar/hooks/useYears.ts b/packages/plasma-hope/src/components/Calendar/hooks/useYears.ts deleted file mode 100644 index bc7f1ca246..0000000000 --- a/packages/plasma-hope/src/components/Calendar/hooks/useYears.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useMemo } from 'react'; - -import { DateObject, YearsItem } from '../types'; -import { YEAR_RENDER_COUNT, isCurrentYear, isSelectedYear, getMatrix } from '../utils'; - -/** - * Хук для получения списка годов. - */ -export const useYears = (date: DateObject, startYear: number) => - useMemo(() => { - // type-coverage:ignore-next-line - const result = Array.from(Array(YEAR_RENDER_COUNT), (_, i) => ({ - isCurrent: isCurrentYear(startYear + i), - isSelected: isSelectedYear(date, startYear + i), - yearValue: startYear + i, - })); - - return getMatrix(result, 3); - }, [date, startYear]); diff --git a/packages/plasma-hope/src/components/Calendar/index.ts b/packages/plasma-hope/src/components/Calendar/index.ts deleted file mode 100644 index f0e7a20e06..0000000000 --- a/packages/plasma-hope/src/components/Calendar/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { CalendarBase } from './CalendarBase'; -export { CalendarDouble } from './CalendarDouble'; -export { Calendar, CalendarBaseRange, CalendarDoubleRange } from './Calendar'; - -export type { CalendarBaseProps, CalendarStateType } from './CalendarBase'; -export type { CalendarDoubleProps } from './CalendarDouble'; -export type { CalendarProps } from './Calendar'; diff --git a/packages/plasma-hope/src/components/Calendar/mixins.ts b/packages/plasma-hope/src/components/Calendar/mixins.ts deleted file mode 100644 index 945715b484..0000000000 --- a/packages/plasma-hope/src/components/Calendar/mixins.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { css, FlattenSimpleInterpolation, StyledComponent } from 'styled-components'; -import { backgroundPrimary, primary, surfaceSolid03, surfaceLiquid02, addFocus, accent } from '@salutejs/plasma-core'; -import { bodySBold } from '@salutejs/plasma-typo'; - -interface Selected { - StyledItem: StyledComponent<'div', {}>; - minWidth: number; - minHeight: number; - isSelected?: boolean; - isCurrent?: boolean; - isHovered?: boolean; -} - -export const flexCenter = css` - display: flex; - justify-content: center; - align-items: center; -`; - -export const flexSpaceBetween = css` - display: flex; - justify-content: space-between; - align-items: center; -`; - -/** - * Функция для установки синтетического фокуса на выбранном дне. - */ -export const syntheticFocus = (ruleset: FlattenSimpleInterpolation, focused?: boolean) => css` - &.focus-visible:focus, - &[data-focus-visible-added] { - outline: none; - z-index: 1; - ${ruleset}; - } - - ${focused && ruleset}; -`; - -/** - * Миксин для установки синтетического фокуса на выбранной кнопке. - */ -export const buttonFocus = css` - ${addFocus({ - outlineRadius: '0.563rem', - outlineSize: '0.063rem', - outlineOffset: '0.125rem', - })}; - - border: none; - background-color: transparent; - padding: 0; -`; - -/** - * Миксин для изменения стиля выбранного дня. - */ -export const selected = ({ StyledItem, minWidth, minHeight, isSelected, isCurrent, isHovered }: Selected) => css` - outline: none; - - ${addFocus({ - synthesizeFocus: syntheticFocus, - outlineRadius: '0.563rem', - outlineSize: '0.063rem', - outlineOffset: isCurrent ? '0.125rem' : '0.063rem', - })}; - - ${isCurrent && - css` - border: 0.063rem solid ${primary}; - `} - - ${isSelected && - css` - ${bodySBold}; - - border: 0; - background-color: ${primary}; - color: ${backgroundPrimary}; - `} - - &:hover { - ${!isSelected && - css` - background-color: ${surfaceLiquid02}; - cursor: pointer; - `} - - ${isCurrent && - !isSelected && - css` - border: 0.063rem solid ${primary}; - background-color: transparent; - color: ${primary}; - - ${StyledItem} { - background-color: ${surfaceLiquid02}; - - min-width: ${minWidth}rem; - min-height: ${minHeight}rem; - } - `} - } - - &, - &:hover { - ${isHovered && - css` - ${bodySBold}; - - background-color: ${accent}; - color: ${backgroundPrimary}; - - cursor: pointer; - `} - } - - &:active { - ${bodySBold}; - - background-color: ${primary}; - color: ${surfaceSolid03}; - } -`; diff --git a/packages/plasma-hope/src/components/Calendar/store/reducer.ts b/packages/plasma-hope/src/components/Calendar/store/reducer.ts deleted file mode 100644 index 1e160555c3..0000000000 --- a/packages/plasma-hope/src/components/Calendar/store/reducer.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { CalendarStateType } from '../types'; -import { getDateFromValue, getNextDate, getPrevDate, getStartYear } from '../utils'; - -import { Action, ActionType, InitialState } from './types'; - -export const getInitialState = ( - value: Date | undefined, - size: [number, number], - calendarState: CalendarStateType, -): InitialState => { - const initDate = value || new Date(); - const date = getDateFromValue(initDate); - - return { - date: { ...date, day: value !== undefined ? date.day : 0 }, - startYear: getStartYear(date.year), - calendarState, - size, - }; -}; - -export const reducer = (state: InitialState, action: Action): InitialState => { - switch (action.type) { - case ActionType.PREVIOUS_MONTH: { - const { year, monthIndex } = action.payload; - const [prevYear, prevMonthIndex] = getPrevDate(year, monthIndex); - - return { - ...state, - date: { - day: state.date.day, - monthIndex: prevMonthIndex, - year: prevYear, - }, - }; - } - case ActionType.NEXT_MONTH: { - const { monthIndex, year } = action.payload; - const [nextYear, nextMonthIndex] = getNextDate(year, monthIndex); - - return { - ...state, - date: { - day: state.date.day, - monthIndex: nextMonthIndex, - year: nextYear, - }, - }; - } - case ActionType.PREVIOUS_YEAR: { - const { step } = action.payload; - - return { - ...state, - startYear: state.startYear - step, - date: { - day: state.date.day, - monthIndex: state.date.monthIndex, - year: state.date.year - step, - }, - }; - } - case ActionType.NEXT_YEAR: { - const { step } = action.payload; - - return { - ...state, - startYear: state.startYear + step, - date: { - day: state.date.day, - monthIndex: state.date.monthIndex, - year: state.date.year + step, - }, - }; - } - case ActionType.PREVIOUS_START_YEAR: { - const { yearsCount } = action.payload; - - return { - ...state, - startYear: state.startYear - yearsCount, - }; - } - case ActionType.NEXT_START_YEAR: { - const { yearsCount } = action.payload; - - return { - ...state, - startYear: state.startYear + yearsCount, - }; - } - case ActionType.UPDATE_DATE: { - const { value } = action.payload; - const date = getDateFromValue(value); - const startYear = getStartYear(value.getFullYear()); - - return { - ...state, - date, - startYear, - }; - } - case ActionType.UPDATE_MONTH: { - const { calendarState, monthIndex, size } = action.payload; - - return { - ...state, - size, - calendarState, - date: { - day: state.date.day, - monthIndex, - year: state.date.year, - }, - }; - } - case ActionType.UPDATE_YEAR: { - const { calendarState, year } = action.payload; - - return { - ...state, - calendarState, - date: { - day: state.date.day, - monthIndex: state.date.monthIndex, - year, - }, - }; - } - case ActionType.UPDATE_CALENDAR_STATE: { - const { calendarState, size } = action.payload; - - return { - ...state, - calendarState, - size: size || state.size, - }; - } - default: - return state; - } -}; diff --git a/packages/plasma-hope/src/components/Calendar/store/types.ts b/packages/plasma-hope/src/components/Calendar/store/types.ts deleted file mode 100644 index 9eb26fb90d..0000000000 --- a/packages/plasma-hope/src/components/Calendar/store/types.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { CalendarStateType, DateObject } from '../types'; - -export interface InitialState { - date: DateObject; - calendarState: CalendarStateType; - startYear: number; - size: [number, number]; -} - -export const enum ActionType { - NEXT_MONTH = 'next_month', - PREVIOUS_MONTH = 'previous_month', - NEXT_YEAR = 'next_year', - PREVIOUS_YEAR = 'previous_year', - PREVIOUS_START_YEAR = 'previous_start_year', - NEXT_START_YEAR = 'next_start_year', - UPDATE_DATE = 'update_date', - UPDATE_MONTH = 'update_month', - UPDATE_YEAR = 'update_year', - UPDATE_CALENDAR_STATE = 'update_calendar_state', -} - -export type PreviousMonthAction = { type: ActionType.PREVIOUS_MONTH; payload: { monthIndex: number; year: number } }; - -export type NextMonthAction = { type: ActionType.NEXT_MONTH; payload: { monthIndex: number; year: number } }; - -export type PreviousYearAction = { type: ActionType.PREVIOUS_YEAR; payload: { step: number } }; - -export type NextYearAction = { type: ActionType.NEXT_YEAR; payload: { step: number } }; - -export type PreviousStartYearAction = { type: ActionType.PREVIOUS_START_YEAR; payload: { yearsCount: number } }; - -export type NextStartYearAction = { type: ActionType.NEXT_START_YEAR; payload: { yearsCount: number } }; - -export type UpdateDateAction = { type: ActionType.UPDATE_DATE; payload: { value: Date } }; - -export type UpdateMonthAction = { - type: ActionType.UPDATE_MONTH; - payload: { calendarState: CalendarStateType; monthIndex: number; size: [number, number] }; -}; - -export type UpdateYearAction = { - type: ActionType.UPDATE_YEAR; - payload: { calendarState: CalendarStateType; year: number }; -}; - -export type UpdateCalendarStateAction = { - type: ActionType.UPDATE_CALENDAR_STATE; - payload: { calendarState: CalendarStateType; size?: [number, number] }; -}; - -export type Action = - | NextMonthAction - | PreviousMonthAction - | NextYearAction - | PreviousYearAction - | PreviousStartYearAction - | NextStartYearAction - | UpdateDateAction - | UpdateMonthAction - | UpdateYearAction - | UpdateCalendarStateAction; diff --git a/packages/plasma-hope/src/components/Calendar/types.ts b/packages/plasma-hope/src/components/Calendar/types.ts deleted file mode 100644 index de3e2e06d8..0000000000 --- a/packages/plasma-hope/src/components/Calendar/types.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { HTMLAttributes, MutableRefObject } from 'react'; - -export enum CalendarState { - Days = 'Days', - Months = 'Months', - Years = 'Years', -} - -export enum Keys { - pageUp = 33, - pageDown = 34, - home = 36, - end = 35, - left = 37, - right = 39, - up = 38, - down = 40, - enter = 13, - space = 32, -} - -export interface DateObject { - day: number; - monthIndex: number; - year: number; -} - -export type CalendarStateType = keyof typeof CalendarState; - -export interface ItemProps { - isCurrent: boolean; - isSelected: boolean; -} - -export interface DateItem extends ItemProps { - isDayInCurrentMonth: boolean; - inRange?: boolean; - date: DateObject; - events?: EventDay[]; - disabled?: boolean; - isOutOfMinMaxRange?: boolean; - disabledArrowKey?: string; - disabledMonths?: string; -} - -export interface DayProps extends Partial { - isDouble?: boolean; - isDayInCurrentMonth?: boolean; - isHovered?: boolean; - inRange?: boolean; - sideInRange?: 'left' | 'right'; - disabled?: boolean; - dayOfWeek?: boolean; -} - -export interface MonthsProps extends ItemProps {} - -export interface MonthsItem extends ItemProps { - monthName: string; - monthFullName?: string; - monthIndex: number; -} - -export interface YearsProps extends ItemProps {} - -export interface YearsItem extends ItemProps { - yearValue: number; -} - -export interface EventDay { - date: Date; - color?: string; -} - -export interface DisabledDay { - date: Date; -} - -export interface UseKeyNavigationProps { - size: [number, number]; - isDouble?: boolean; - onPrev: (withShift?: boolean) => void; - onNext: (withShift?: boolean) => void; -} - -export type CalendarValueType = Date | undefined | [Date | undefined, Date?]; - -export interface Calendar extends HTMLAttributes { - /** - * Выбранное значение. - */ - value: CalendarValueType; - /** - * Состояние календаря, отвечающее за отображение. - */ - date?: DateObject; - /** - * Минимальное значение даты. - */ - min?: Date; - /** - * Максимальное значение даты. - */ - max?: Date; - /** - * Список событий. - */ - eventList?: EventDay[]; - /** - * Список отключенных дней. - */ - disabledList?: DisabledDay[]; - /** - * Обработчик изменения значения. - */ - onChangeValue: (value: Date) => void; -} - -export type CalendarRange = Omit & { - /** - * Выбранное значение. - */ - value: [Date, Date?]; - /** - * Обработчик изменения значения. - */ - onChangeValue: (values: [Date, Date?]) => void; -}; - -export interface DaysMetaDescription { - refs: MutableRefObject; - rowSize: number; - newRowIndex: number; - newColumnIndex: number; - columnSize: number; - minColumnIndex: number; - defaultState?: number[]; -} - -export type KeyboardArrowKey = 'left' | 'right' | 'up' | 'down'; diff --git a/packages/plasma-hope/src/components/Calendar/utils.ts b/packages/plasma-hope/src/components/Calendar/utils.ts deleted file mode 100644 index d31ccaa591..0000000000 --- a/packages/plasma-hope/src/components/Calendar/utils.ts +++ /dev/null @@ -1,295 +0,0 @@ -import type { CalendarValueType, DateObject, DisabledDay, ItemProps } from './types'; -import { CalendarStateType } from './types'; - -export const ROW_STEP = 6; - -export const YEAR_RENDER_COUNT = 12; - -export const SHORT_DAY_NAMES = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] as const; - -export const FULL_DAY_NAMES: Record = { - Пн: 'Понедельник', - Вт: 'Вторник', - Ср: 'Среда', - Чт: 'Четверг', - Пт: 'Пятница', - Сб: 'Суббота', - Вс: 'Воскресенье', -}; - -export const SHORT_MONTH_NAME = ['Янв', 'Фев', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек']; - -export const MONTH_NAMES = [ - 'Январь', - 'Февраль', - 'Март', - 'Апрель', - 'Май', - 'Июнь', - 'Июль', - 'Август', - 'Сентябрь', - 'Октябрь', - 'Ноябрь', - 'Декабрь', -]; - -export const getDaysInMonth = (monthIndex: number, year: number) => new Date(year, monthIndex + 1, 0).getDate(); - -export const getOffsetDayInWeek = (monthIndex: number, year: number) => (new Date(year, monthIndex).getDay() || 7) - 1; - -export const getStartYear = (year: number) => Math.trunc((year - 1) * 0.1) * 10 - 1; - -export const getNextDate = (currentYear: number, currentMonth: number) => - currentMonth + 1 === MONTH_NAMES.length ? [currentYear + 1, 0] : [currentYear, currentMonth + 1]; - -export const getPrevDate = (currentYear: number, currentMonth: number) => - currentMonth - 1 < 0 ? [currentYear - 1, 11] : [currentYear, currentMonth - 1]; - -export const getDateFromValue = (date: Date | undefined): DateObject => { - const state = date || new Date(); - - return { - day: date !== undefined ? state.getDate() : 0, - monthIndex: state.getMonth(), - year: state.getFullYear(), - }; -}; - -export const getDateFromNow = (): DateObject => { - const nowDate = new Date(); - - return { - day: nowDate.getDate(), - monthIndex: nowDate.getMonth(), - year: nowDate.getFullYear(), - }; -}; - -export const IsCurrentDay = (date: DateObject, currentDay: number) => { - const { day, monthIndex: currentMonthIndex, year: currentYear } = getDateFromNow(); - return day === currentDay && date.monthIndex === currentMonthIndex && date.year === currentYear; -}; - -export const isSelectedDay = (date: DateObject, currentDay: number, value?: Date) => { - if (!value) { - return false; - } - - const { day, monthIndex, year } = getDateFromValue(value); - return day === currentDay && date.monthIndex === monthIndex && date.year === year; -}; - -export const isCurrentMonth = (date: DateObject, monthIndex: number) => { - const { monthIndex: currentMonthIndex, year: currentYear } = getDateFromNow(); - return monthIndex === currentMonthIndex && date.year === currentYear; -}; - -export const isSelectedMonth = (date: DateObject, monthIndex: number) => date.monthIndex === monthIndex; - -export const isCurrentYear = (year: number) => { - const { year: currentYear } = getDateFromNow(); - return year === currentYear; -}; - -export const isSelectedYear = (date: DateObject, year: number) => date.year === year; - -export const getSortedValues = (values: [Date | undefined, (Date | undefined)?]) => - values.sort((start, end) => { - if (!start || !end) { - return -1; - } - - return start.getTime() - end.getTime(); - }); - -export const isDayInRage = ( - year: number, - monthIndex: number, - currentDay: number, - values: [Date | undefined, Date?], -) => { - const [startValue, endValue] = getSortedValues(values); - - if (!endValue) { - return false; - } - - const day = new Date(year, monthIndex, currentDay); - return startValue && startValue <= day && day <= endValue; -}; - -export const isSameDay = (firstDate: DateObject, secondDate?: DateObject) => - secondDate && - firstDate.day === secondDate.day && - firstDate.monthIndex === secondDate.monthIndex && - firstDate.year === secondDate.year; - -export const isValueUpdate = (value: Date | [Date, Date?], prevValue: Date | [Date, Date?]) => { - if (!Array.isArray(value) && !Array.isArray(prevValue)) { - return prevValue?.getTime() !== value?.getTime(); - } - - if (Array.isArray(value) && Array.isArray(prevValue)) { - return prevValue[0]?.getTime() !== value[0]?.getTime() || prevValue[1]?.getTime() !== value[1]?.getTime(); - } - - return false; -}; - -/** - * Метод проверяет, находится ли календарь в режиме выбора второго значения. - */ -export const isSelectProcess = (array: unknown | unknown[]): array is [Date, Date?] => - Array.isArray(array) && !array[1]; - -/** - * Метод возвращает сторону, когда выбор второго значения диапазона завершён. - */ -export const getSideForSelected = (date: DateObject, startValue: Date, endValue: Date) => { - const currentDateTime = new Date(date.year, date.monthIndex, date.day).getTime(); - const startValueTime = startValue.getTime(); - const endValueTime = endValue.getTime(); - - if (currentDateTime === startValueTime) { - return 'right'; - } - - if (currentDateTime === endValueTime) { - return 'left'; - } - - return undefined; -}; - -/** - * Метод возвращает сторону, во время выбора второго значения диапазона. - */ -export const getSideForHovered = (date: DateObject, hoveredDay: DateObject, startValue: Date, isSelected?: boolean) => { - const dateHover = new Date(hoveredDay.year, hoveredDay.monthIndex, hoveredDay.day); - const isHovered = isSameDay(date, hoveredDay); - - if ((isSelected && startValue > dateHover) || (isHovered && startValue < dateHover)) { - return 'left'; - } - - if ((isSelected && startValue < dateHover) || (isHovered && startValue > dateHover)) { - return 'right'; - } - - return undefined; -}; - -/** - * Метод возвращает сторону, с которой нужно отрисовать направление полоски диапазона. - */ -export const getSideInRange = ( - value: CalendarValueType, - date: DateObject, - hoveredDay?: DateObject, - isSelected?: boolean, -) => { - if (!Array.isArray(value)) { - return undefined; - } - - const [startValue, endValue] = value; - - if (startValue && isSelected && endValue) { - return getSideForSelected(date, startValue, endValue); - } - - if (startValue && hoveredDay) { - return getSideForHovered(date, hoveredDay, startValue, isSelected); - } - - return undefined; -}; - -/** - * Метод проверяет, находится ли выбранный день в диапазоне. - */ -export const getInRange = (value: CalendarValueType, date: DateObject, hoveredDay?: DateObject, inRange?: boolean) => { - if (!isSelectProcess(value) || !hoveredDay) { - return inRange; - } - - const dateSelected = value[0]; - const dateHover = new Date(hoveredDay.year, hoveredDay.monthIndex, hoveredDay.day); - const dateCurrent = new Date(date.year, date.monthIndex, date.day); - - if ( - (dateSelected < dateCurrent && dateCurrent < dateHover) || - (dateSelected > dateCurrent && dateCurrent > dateHover) - ) { - return true; - } - - return inRange; -}; - -/** - * Метод проверяет, можно ли выбрать день. - */ -export const canSelectDate = ( - { year, monthIndex, day }: DateObject, - value: CalendarValueType, - disabledList?: DisabledDay[], -) => { - if (!isSelectProcess(value)) { - return true; - } - - const hoverDate = new Date(year, monthIndex, day); - const [startDate] = value; - - if (hoverDate?.getTime() === startDate?.getTime()) { - return false; - } - - if (!disabledList?.length) { - return true; - } - - const offDisabledRange = disabledList.some( - ({ date }) => (startDate < date && date < hoverDate) || (startDate > date && date > hoverDate), - ); - - return !offDisabledRange; -}; - -/** - * Метод для получения двумерного массива и возвращения выбранного элемента. - */ -export const getMatrix = (items: T[], rowSize = 7): readonly [T[][], number[] | undefined] => { - const newItems = [...items]; - - let selected: [number, number] | undefined; - - const result = newItems.reduce((acc: T[][], item, index) => { - if (index % rowSize === 0) { - acc.push([]); - } - - acc[acc.length - 1].push(item); - - if (item.isSelected) { - selected = [acc.length - 1, index % rowSize]; - } - - return acc; - }, []); - - return [result, selected] as const; -}; - -export const getCalendarType = (type: CalendarStateType) => { - switch (type) { - case 'Months': - return 'год'; - case 'Years': - return 'период'; - default: - return 'месяц'; - } -}; diff --git a/packages/plasma-hope/src/components/Calendar/withRange.tsx b/packages/plasma-hope/src/components/Calendar/withRange.tsx deleted file mode 100644 index 7082519209..0000000000 --- a/packages/plasma-hope/src/components/Calendar/withRange.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo, useState, useCallback, ReactElement } from 'react'; - -import type { Calendar, CalendarRange } from './types'; -import { getSortedValues, isValueUpdate } from './utils'; - -/** - * HOC для календаря, дающий возможность выбора диапазона даты - */ -export const withRange = (Component: React.FC) => ({ - value, - disabledList, - eventList, - min, - max, - onChangeValue, - ...rest -}: CalendarRange): ReactElement => { - const [startExternalValue, endExternalValue] = useMemo(() => value, [value]); - const [[startValue, endValue], setValues] = useState<[Date, Date?]>([startExternalValue, endExternalValue]); - const [prevValue, setPrevValue] = useState(value); - - if (isValueUpdate(value, prevValue)) { - setValues([startExternalValue, endExternalValue]); - - setPrevValue(value); - } - - const handleOnChangeDay = useCallback( - (newDay: Date) => { - if (endValue) { - setValues([newDay, undefined]); - return; - } - - setValues([startValue, newDay]); - - const [first, second] = getSortedValues([startValue, newDay]); - - if (first) { - onChangeValue([first, second]); - } - }, - [onChangeValue, startValue, endValue], - ); - - return ( - - ); -}; diff --git a/packages/plasma-hope/src/index.ts b/packages/plasma-hope/src/index.ts index 647ca03d56..7fbf6ca01f 100644 --- a/packages/plasma-hope/src/index.ts +++ b/packages/plasma-hope/src/index.ts @@ -1,7 +1,6 @@ export * from './components/AudioPlayer'; export * from './components/Badge'; export * from './components/Button'; -export * from './components/Calendar'; export * from './components/Card'; export * from './components/Carousel'; export * from './components/Cell';