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

refactor: ModalRoot/ModalPage/ModalCard #6759

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

inomdzhon
Copy link
Contributor

@inomdzhon inomdzhon commented Mar 26, 2024

Проверить после


  • Unit-тесты
  • e2e-тесты
  • Дизайн-ревью
  • Документация фичи
  • Release notes

Описание

Переписаны компоненты ModalPage/ModalCard

⚠️ Теперь компоненты могут использоваться без ModalRoot.

ModalPage / ModalCard:

  • добавлено свойство open;
  • добавлено свойство keepMounted;
  • типа onClose изменён с VoidFunction на (reason: ModalPageCloseReason, event?: UIEvent<HTMLElement>) => void;
  • добавлено свойство noFocusToDialog, приоритетней чем noFocusToDialog в ModalRoot;
  • добавлено свойство modalOverlayTestId, приоритетней чем modalOverlayTestId в ModalRoot.
  • создан компонент ModalOutlet;
  • создан компонент ModalOverlay;
  • основной код работы модалок вынесен в отдельные компоненты ModalPageInternal и ModalCardInternal, у них есть свойство ModalOverlay;
  • создан контекст ModalContext, который теперь используется в компонентах ModalPageHeader, Group и PanelHeader вместо ModalRootContext.

ModalPage:

  • settlingHeight теперь имеет значение по умолчанию 50% вместо 75%, обратил внимание, что в обычно BottomSheet'ы открываются на половину экрана;
  • добавлено свойство footer, а также создан компонент ModalPageFooter;
  • созданы компонент ModalPageContent – вынес, чтобы можно было в будущем перейти на сбор ModalPage через композицию компонентов.

lib/sheet:

В папке хранится логика отвечающая за взаимодействие с модалкой на мобильных экранах. Отдаёт хук useBottomSheet().

Переписан компонент ModalRoot

⚠️ Теперь лишь отвечает за состояние open компонентов ModalCard и ModalPage в зависимости от их id/nav и параметра activeModal, а также рендера общей ModalOverlay для всех модалок.

ModalRoot:

  • добавлены события onOpen, onOpened, onClosed, которые всплывают от activeModal;
  • в PopoutRoot удалён PopoutRootModal в пользу ModalOutlet у ModalPage и ModalCard.

ModalRootContext:

  • ⚠️ BREAKING CHANGE в свойство onClose нужно теперь обязательно передавать id модального окна;
  • добавлены события onOpen, onOpened, onClosed, чтобы их могли вызывать ModalPage и ModalCard;
  • свойство registerModal теперь @deprecated – не нужно отдельно регистрировать модальное окно.
  • свойство updateModalHeight теперь @deprecated – задача с обновлением высоты контента при dynamicContentHeight решается через CSS;

ModalRootOverlayContext / VisuallyHiddenModalOverlay:

  • чтобы создать общий ModalOverlay для всех модалок в контексте ModalRoot, происходит подмена ModalOverlay в ModalPage и ModalCard на VisuallyHiddenModalOverlay, который отвечает за приём onClick и modalOverlayTestId, а сам ModalOverlay попадает в начало ModalRoot

useModalManager():

  • отвечает за unmounted состояние;
  • отвечает за регулирования приоритета параметров из ModalRoot и ModalPage/ModalCard;
  • отвечает за подмену ModalOverlay на VisuallyHiddenModalOverlay.

withModalRootContext:

  • ввиду отказа от updateModalHeight HOC тоже @deprecated.

Изменения, которые нужно вынести в отдельные PR после первичного ревью

Нюансы

Обратная совместимость

Постарался сделать так, чтобы миграция прошла бесследно.

Сломаются вот такой кейс:

const MyModal = ({ id }) => { // пропустили settinglingHeight / dynamicContentHeight
  return <ModalPage>Lorem Ipsum</ModalPage>
};

const App = () => (
  <ModalRoot>
    <MyModal
      id="example-1"
      settinglingHeight={100} // устанавливалось здесь, т.к. раньше ModalRoot итерировал по потомкам и доставал этот параметр
    />
    
    <MyModal
      id="example-2"
      dynamicContentHeight // устанавливалось здесь, т.к. раньше ModalRoot итерировал по потомкам и доставал этот параметр
    />
  </ModalRoot>
);

который нужно будет править руками.

BottomSheet, анимации и свайп

Полное появление и полное скрытие происходит через transform, но анимация взаимодействия через свайп реализована через height, т.к. это оказалось самым оптимальным способом для решения задач:

  • закреплённый ModalPageFooter внизу;
  • возможность скроллить при settlingHeight меньше 100;
  • обновление высоты, если задан dynamicContentHeight.

Нашёл решение допустимым, т.к. свайп используется либо для закрытия, либо для разворачивания/сворачивания модального окна на всю или на половину страницы. В первом случае сработает закрытие через transform, а во втором разворачивание/сворачивание произойдёт через height.

dynamicContentHeight

см. предыдущий пункт про BottomSheet для контекста

Обновление высоты происходит без анимации, т.к. height: auto не анимируется. Опустил анимирование, т.к. усложняет компонент. В теории можно прибегнуть к useResizeObserver().

Адативность

При platform="vkcom" и при разрешении экрана 767px компонент теперь превращается в BottomSheet, но при этом логику взаимодействия через тач не имеет, т.к. isDesktop при platform="vkcom" всегда true вне зависимости от размера экрана.

Решения

Выделение текста

С помощью функции hasSelectionWithRangeType определяем, что пользователь выделил текст и перестаём реагировать на touchstart и touchmove пока выделение не будет удалено.

Вертикальный и горизонтальный скроллы

  • вертикальный скролл::
    • основной скролл (ModalPageContent): проверяем на scrollTop !== 0
    • другие скроллы: при touchstart достаём скроллируемый элемент через event.target и проверяем на положение scrollTop !== 0 и направление пальца вверх
  • горизонтальный скролл: не блочится, т.к. реагируем на события только по оси Y.

Плавающие элементы внутри модалки

Нужно рекомендовать использовать forcePortal – в коде проверяем, что идёт взаимодействие с элементом вне модалки.

Или нужно рекомендовать добавлять в корневой элемент плавающего элемента атрибут data-vkui-prevent-swipe .

Поля ввода

Наилучшего варианта не нашёл кроме как:

  1. через useVirtualKeyboardState() узнавать, что пользователь работает с клавиатурой, и перебивать safe-area-inset-bottom на тот, что возвращает хук 0 (попытка вычислять разницу высоты через VisualViewport, чтобы реагировать на смену размера клавиатуры, например, из-за панели эмодзи, не удалась);
  2. в том же хуке слушать событие скролла на window и сохранять его позицию на window.scrollTo(0, visualViewport.offsetTop).

Так как иные решения приводят к другим проблемам (подробнее можно прочесть в JSDoc хука useVirtualKeyboardState()), следующие баги нужно закрыть:

Референсы

Copy link
Contributor

github-actions bot commented Mar 26, 2024

size-limit report 📦

Path Size
JS 384.1 KB (-0.36% 🔽)
JS (gzip) 116.18 KB (-0.57% 🔽)
JS (brotli) 95.69 KB (-0.45% 🔽)
JS import Div (tree shaking) 1.56 KB (0%)
CSS 337.19 KB (+0.66% 🔺)
CSS (gzip) 42.8 KB (+0.92% 🔺)
CSS (brotli) 33.7 KB (+0.84% 🔺)

Copy link

codesandbox-ci bot commented Mar 26, 2024

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit d567e6e:

Sandbox Source
vkui boilerplate (forked) Issue #1494

Copy link
Contributor

github-actions bot commented Mar 26, 2024

e2e tests

Playwright Report

Copy link
Contributor

github-actions bot commented Mar 26, 2024

👀 Docs deployed

Commit d567e6e

Copy link

codecov bot commented Mar 26, 2024

Codecov Report

Attention: Patch coverage is 64.53901% with 200 lines in your changes missing coverage. Please review.

Project coverage is 93.92%. Comparing base (d19c0bf) to head (d567e6e).

Files with missing lines Patch % Lines
...src/lib/sheet/controllers/BottomSheetController.ts 25.00% 147 Missing ⚠️
...c/lib/sheet/controllers/CSSTransitionController.ts 32.25% 21 Missing ⚠️
...kui/src/components/ModalPage/ModalPageInternal.tsx 88.00% 9 Missing ⚠️
packages/vkui/src/lib/sheet/useBottomSheet.ts 88.09% 5 Missing ⚠️
...kages/vkui/src/lib/touch/UIPanGestureRecognizer.ts 28.57% 5 Missing ⚠️
...kui/src/components/ModalCard/ModalCardInternal.tsx 92.15% 4 Missing ⚠️
packages/vkui/src/lib/dom.tsx 69.23% 4 Missing ⚠️
packages/vkui/src/lib/adaptivity/functions.ts 60.00% 2 Missing ⚠️
packages/vkui/src/lib/sheet/index.ts 60.00% 2 Missing ⚠️
.../vkui/src/components/ModalOverlay/ModalOverlay.tsx 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6759      +/-   ##
==========================================
- Coverage   95.47%   93.92%   -1.55%     
==========================================
  Files         380      389       +9     
  Lines       11325    11182     -143     
  Branches     3777     3699      -78     
==========================================
- Hits        10812    10503     -309     
- Misses        513      679     +166     
Flag Coverage Δ
unittests 93.92% <64.53%> (-1.55%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@vkcom-publisher vkcom-publisher added pr-needs-work Автоматизация: PR автоматически закроется через 14 дней при отсутствии активности and removed pr-needs-work Автоматизация: PR автоматически закроется через 14 дней при отсутствии активности labels Apr 3, 2024
@inomdzhon inomdzhon added the no-stale Добавляет PR в исключения для автоматического закрытия label Apr 6, 2024
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 3 times, most recently from 4a0d455 to 7d03282 Compare May 16, 2024 16:03
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 2 times, most recently from cd0da9c to c382b8f Compare August 1, 2024 14:58
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch from c382b8f to fba65d5 Compare September 30, 2024 09:04
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 3 times, most recently from c1453c5 to a1c9447 Compare October 28, 2024 23:33
@inomdzhon inomdzhon changed the title [Draft] feat: create ModalPageV2 refactor: ModalRoot/ModalPage/ModalCard Oct 28, 2024
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 6 times, most recently from 481fc96 to f07b8b4 Compare October 30, 2024 15:25
@inomdzhon inomdzhon marked this pull request as ready for review October 30, 2024 15:25
@inomdzhon inomdzhon requested a review from a team as a code owner October 30, 2024 15:25
Copy link
Contributor

@andrey-medvedev-vk andrey-medvedev-vk left a comment

Choose a reason for hiding this comment

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

Грандиозная работа! 👏 👏 👏 💪

Здорово раскидал по компонентам!
Понравилось, что компоненты теперь функции как практически везде!
Офигенно как переведена анимация/перетаскивание на CSS Transition и контроллеры. (Там я, конечно, поплыл, но приятно видеть знакомое API с коллбэками и то, что всё сводится к установке css переменных)


Надо пристально пройтись по местам где коллбёки передаются в компоненты, а не напрямую в html элементы и обернуть их useCallback, и по местам, где объекты возвращаются из хуков или передаются в контекст, чтобы обернуть в useMemo и избежать лишних ререндеров.


По стайлгайду и сторибуку прошелся и так и на устройстве, ничего такого не заметил.

packages/vkui/src/components/ModalRoot/ModalRoot.tsx Outdated Show resolved Hide resolved
packages/vkui/src/hooks/useFocusTrap.ts Outdated Show resolved Hide resolved
packages/vkui/src/hooks/useVirtualKeyboardState.ts Outdated Show resolved Hide resolved
packages/vkui/src/lib/dom.tsx Outdated Show resolved Hide resolved
@inomdzhon
Copy link
Contributor Author

Надо пристально пройтись по местам где коллбёки передаются в компоненты, а не напрямую в html элементы и обернуть их useCallback, и по местам, где объекты возвращаются из хуков или передаются в контекст, чтобы обернуть в useMemo и избежать лишних ререндеров.

добавил мемоизацию там где можно, но во всех местах сильная завязка на пользователя, т.к. используются колбеки (onOpen, onOpened, onClose, onClosed), которые пользователь сам должен мемоизировать

ждём React 19, где не нужно будет руками мемоизировать)

@inomdzhon inomdzhon mentioned this pull request Nov 1, 2024
1 task
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch from cb1bd66 to 9473173 Compare November 2, 2024 14:46
inomdzhon added a commit that referenced this pull request Nov 5, 2024
- `CustomSelect` жалуется на `renderOption` в 729 строке, что якобы идёт обращение к `ref.current`, но такого там нет;
    ```plain
    729:51  error  Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)  react-compiler/react-compiler
    ```
- `ModalRootContext` и `useModalManager` переписываются в PR #6759.
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 3 times, most recently from eb4002f to 2aeae94 Compare November 8, 2024 15:45
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch from 2aeae94 to 45e4dff Compare November 18, 2024 13:04
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch 2 times, most recently from 87edb94 to 8132551 Compare November 20, 2024 15:36
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch from 8132551 to 6654031 Compare November 21, 2024 15:05
@inomdzhon inomdzhon force-pushed the imirdzhamolov/research/refactor-modal branch from 55d7a16 to d567e6e Compare November 25, 2024 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment