Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Calendar): revert period #3415

Open
wants to merge 11 commits into
base: 4.17.0/datepicker
Choose a base branch
from
Open

fix(Calendar): revert period #3415

wants to merge 11 commits into from

Conversation

zhzz
Copy link
Member

@zhzz zhzz commented Apr 25, 2024

Проблема

В #3258 были добавлены пропы для периода дат и проведен некоторый рефакторинг.

Пропы для периода планируется использовать только внутри отдельного компонента PeriodPicker, но они значительно усложняют устройство самого Calendar. PeriodPicker может реализовать ту же функциональность внутри себя самостоятельно, используя более общий проп renderDay. Это не задача самого календаря. Он может оставаться простым. Поэтому считаю, что пропы для периода в Calendar не нужны. Их стоит откатить.

Проведенная в #3258 замена пропа onMonthChange на onStuckMonth и onMonthSelect кажется нецелесообразной. onMonthChange решает ту же задачу, но является более общим. Он не зависит от реализации смены месяцев (с залипанием или без). Возможно, его стоит переименовать на onVisibleMonthChange для большей интуитивности.

В #3258 также был добавлен debounce на вызов onStuckMonth. Его тоже предлагаю откатить. Во-первых, вызовов происходит не очень много. Debounce уменьшает количество вызовов на единицы. Что не очень существенно (но добавляет немного сложности). Во-вторых, не факт, что пользователю не будут интересны все вызовы. Среди них нет лишних или дублирующих. Они все отражают факт смены месяца. Так что, думаю, их не стоит debounce-ить.

Решение

Откачено

  1. Удалил пропы periodStartDate и periodEndDate
  2. Удалил тесты на период
  3. Вернул onMonthChange вместо onStuckMonth и onMonthSelect
  4. Убрал debounce для onMonthChange
  5. Откатил темные скриншоты Calendar и DatePicker, изменившиеся в feat(Calendar): added period #3258

Переделано

  1. Поменял сигнатуру пропа renderDay. Теперь он опирается на публичный компонент CalendarDay
  2. Переделал примеры с периодом и с кастомным днем

Добавлено новое

  1. Публичный компонент CalendarDay
  2. Хелперы для конвертации дат в CalendarDateShape. Они понадобятся в разработке PeriodPicker
  3. Оптимизировал рендер дня через memo. Так как с добавлением CalendarContext сильно просел перфоманс анимации скролла из-за тысяч ререндеров дней.

Переделано дополнительно

  1. Упростил верстку ячеек дней, с вынесением общих стилей
    • Избавился от динамических inline-стилей в пустых ячейках
    • Поменял .today2022 .todayCaption на .todayCaption2022

Удалено дополнительно

  1. Скриншот на onMonthChange. Он был добавлен давно. Проблем с ним не было, за исключением того, что тест был неочевидный. Тот же функционал тестируется юнитами. Чтобы не переделывать в нем renderDay, я удалил его за ненадобностью.

Ссылки

IF-1702

Чек-лист перед запросом ревью

  1. Добавлены тесты на все изменения
    ✅ unit-тесты для логики
    ✅ скриншоты для верстки и кросс-браузерности
    ⬜ нерелевантно

  2. Добавлена (обновлена) документация
    ✅ styleguidist для пропов и примеров использования компонентов
    ⬜ jsdoc для утилит и хелперов
    ⬜ комментарии для неочевидных мест в коде
    ⬜ прочие инструкции (README.md, contributing.md и др.)
    ⬜ нерелевантно

  3. Изменения корректно типизированы
    ✅ без использования any (см. PR 2856)
    ⬜ нерелевантно

  4. Прочее
    ✅ все тесты и линтеры на CI проходят
    ✅ в коде нет лишних изменений
    ✅ заголовок PR кратко и доступно отражает суть изменений (он попадет в changelog)

@zhzz zhzz changed the title fix(Calendar): update period fix(Calendar): revert period Apr 25, 2024
@zhzz zhzz mentioned this pull request Apr 25, 2024
@zhzz zhzz marked this pull request as ready for review April 27, 2024 08:53
@zhzz zhzz requested a review from lossir April 27, 2024 11:32
packages/react-ui/components/Calendar/Calendar.md Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/CalendarDay.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/CalendarDay.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/CalendarDay.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/CalendarDay.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/Calendar.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/DayCellView.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/Month.tsx Outdated Show resolved Hide resolved
packages/react-ui/components/Calendar/Calendar.tsx Outdated Show resolved Hide resolved
@zhzz zhzz requested a review from mshatikhin May 3, 2024 08:28
packages/react-ui/components/Calendar/Calendar.md Outdated Show resolved Hide resolved

export const fromString = (dateString: string): CalendarDateShape => {
const [date, month, year] = dateString.split('.').map(Number);
return create(date, getMonthInNativeFormat(month), year);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю, изначально было правильно - без getMonthInNativeFormat().

В нашем строковом представлении даты месяц начинается с 01. Это видно по старым примерам для Календаря, ДатаПикера и ДатаИнпута.

В примере в доке можно сразу создавать объект вида { year, month, date }, ведь CalendarDay предполагается использовать внутри renderDay, а там всегда приходит такой объект.


Если хелпер полезен для сторисов, то лучше использовать InternalDate, который уже умеет парсить и трансформировать дату.

new InternalDate({ value: dateString }).getComponentsLikeNumber()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переосознал, переделал и подрезюмировал ниже механизмы работы с датами и форматами.

Comment on lines 38 to 40
const ariaLabel = `${locale.dayCellChooseDateAriaLabel}: ${new InternalDate({ langCode })
.setComponents({ ...date }, true)
.toA11YFormat()}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь date должен приходить с "человеческим" представлением месяца, где январь 1.

Так мы сохраним консистентность публичной работы с датами в наших контролах.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В итоге ariaLabel и так получался в человеческом формате благодаря второму аргументу в setComponents.

Но в любом случае, я перевел CalendarDay целиком на работу с человеческим форматом.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, тейк был именно в том, что приходится использовать второй аргумент, т.к. в пропах пользователь вынужден будет передавать нативный месяц.

Comment on lines 61 to 64
export const toString = ({ date, month, year }: CalendarDateShape): string => {
const [d, m, y] = [date, getMonthInHumanFormat(month), year].map((x) => x.toString());
return `${d.padStart(2, '0')}.${m.padStart(2, '0')}.${y.padStart(4, '0')}`;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот хелпер тоже повторяет возможности InternalDate

new InternalDate().setComponents({ date, month, year }).toString({ withPad: true })

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отписал ниже свои предложения по использованию CalendarDateShape и InternalDate

@zhzz
Copy link
Member Author

zhzz commented May 15, 2024

По поводу CalendarDateShape и InternalDate отпишу тут, в одном месте.

У нас наблюдаются два пересекающийхся по функционалу механизма для работы с датами. Это CalendarDateShape и InternalDate. Я не в курсе всей истории и замыслов их создания. Но по косвенным признакам могу предположить следующее.

InternalDate - это внутренний подкапотный механизм. Он содержит кучу логики, о которой пользователю знать нет нужды. И вроде бы он нигде не торчит наружу.

CalendarDateShape - это публичный механизм. Он явным образом экспортируется из календаря. Он доступен пользователю и ограничен теми функциями, которые могут ему реально понадобиться для манипуляции с датами там, где строки не удобны. Например, для работы с периодами.

Поэтому предлагаю считать CalendarDateShape публичным API, описать его в доке и использовать в примерах только его.

InternalDate лучше оставить только для внутренних нужд компонентов и, когда-нибудь, навести в нем порядок.

@zhzz
Copy link
Member Author

zhzz commented May 15, 2024

По поводу формата месяцев.

Их тоже в компонентах наблюдается два: человеческий и нативный (начинающий отсчет с 0). До сих пор все даты, которые передавал или получал пользователь из контрола, мы старались предлагать исключительно в человеческом формате. Нативный же используется во внутрянке.

В этом ПР появляется еще одно место соприкосновения пользователя с датой. Это проп date в CalendaDay. Изначально я не сильно раздумывал и оставил его в нативном формате. Но после ревью и составления всей картины выше стала очевидна непоследовательность этого API для пользователя.

В итоге, предлагаю CalendaDay, как публичный компонент, тоже перевести на человеческий формат. Это позволяет также совсем избавиться от работы с форматами внутри CalendarDateShape и не заставлять пользователя вообще знать про нативный формат.

@zhzz zhzz requested a review from lossir May 15, 2024 10:10
@lossir
Copy link
Member

lossir commented May 20, 2024

InternalDate появился как альтернатива Moment.js, способная в интернациализацию описанную тут #1277 и в фичи из Гайда.
Тогда большой рефакторинг касался только компонента DateInput и InputLikeText, поэтому в DatePicker и Calendar остались старые решения.

CalendarDateShape это самая первая реализация даты в пакете. В этих хелперах дата представлена совсем абстрактно - буквально объект чисел. Тут нет понятия "нативный формат месяца", или "человеческое отображение даты".

Эти хелперы экспортируется из корня Календаря, и после его опубличивания они тоже стали публичными:

import { isEqual, comparator } from '@skbkontur/react-ui';

В своё время мы отбились от вопросов "почему строка, а не Moment/Date", и теперь могут возникнут вопросы "почему объект, а не Moment/Date".
По-моему правильным решением будет научить InternalDate сравнивать даты для внутреннего использования, и задеприкейтить хэлперы CalendarDateShape.

В идеале будет отрефачить и сам InternakDate, перейдя на нативную дату и Internationalization API.


Если в CalendarDay проп дата будет объектом чисел, то пользователь может сам их сравнивать без каких-либо хелперов.
Если же мы предлагаем ему хэлперы, то думаю не правильно делать в них АПИ отличное от компонентов DateInput, DatePicker и Calendar.

Т.е. я бы предложил проп date в CalendarDay сделать строкой, как и в других компонентах.
И не предоставлять никаких хэлперов пока не будет запроса.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants