diff --git a/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.test.tsx b/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.test.tsx index 5805b0df00..7db36c09c6 100644 --- a/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.test.tsx +++ b/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.test.tsx @@ -1,43 +1,26 @@ import * as React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { baselineComponent, fakeTimers, runAllTimers } from '../../testing/utils'; import { PopoutWrapper } from './PopoutWrapper'; +import styles from './PopoutWrapper.module.css'; -// TODO: Warning: An update to PopoutWrapper inside a test was not wrapped in act(...) - -describe('PopoutWrapper', () => { +describe(PopoutWrapper, () => { baselineComponent(PopoutWrapper); - describe('prevents touchmove', () => { - it('while mounted', () => { - render(); - const e = new TouchEvent('touchmove'); - const preventDefault = jest.spyOn(e, 'preventDefault'); - fireEvent(window, e); - expect(preventDefault).toBeCalled(); - }); - it('clears after unmount', () => { - const h = render(); - h.unmount(); - const e = new TouchEvent('touchmove'); - const preventDefault = jest.spyOn(e, 'preventDefault'); - fireEvent(window, e); - expect(preventDefault).not.toBeCalled(); - }); - }); - - describe('gets opened', () => { - const isOpened = () => !!document.querySelector('.vkuiPopoutWrapper--opened'); + describe('opened state', () => { fakeTimers(); - it('immediately if no mask', () => { - render(); - expect(isOpened()).toBe(true); + + it('should be opened immediately if no mask', () => { + const result = render(); + expect(result.getByTestId('popout-wrapper')).toHaveClass(styles['PopoutWrapper--opened']); }); - it('after animation if mask', () => { - render(); - expect(isOpened()).toBe(false); + + it('should be opened after animation if has mask', () => { + const result = render(); + const locator = result.getByTestId('popout-wrapper'); + expect(locator).not.toHaveClass(styles['PopoutWrapper--opened']); runAllTimers(); - expect(isOpened()).toBe(true); + expect(locator).toHaveClass(styles['PopoutWrapper--opened']); }); }); }); diff --git a/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.tsx b/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.tsx index a5afecd586..56e08823ea 100644 --- a/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.tsx +++ b/packages/vkui/src/components/PopoutWrapper/PopoutWrapper.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; -import { useGlobalEventListener } from '../../hooks/useGlobalEventListener'; import { usePlatform } from '../../hooks/usePlatform'; import { useTimeout } from '../../hooks/useTimeout'; -import { useDOM } from '../../lib/dom'; import { HTMLAttributesWithRootRef } from '../../types'; import { RootComponent } from '../RootComponent/RootComponent'; import styles from './PopoutWrapper.module.css'; @@ -21,10 +19,27 @@ const stylesAlignY = { }; export interface PopoutWrapperProps extends HTMLAttributesWithRootRef { + /** + * Если `true`, то при первом монтировании оверлей появится через fade-in анимацию. + */ hasMask?: boolean; + /** + * Включает фиксированное позиционирование. + * + * По умолчанию у компонента не задан никакой `position`. + */ fixed?: boolean; - alignY?: 'top' | 'center' | 'bottom'; + /** + * Выравнивает контент по горизонтали. + */ alignX?: 'left' | 'center' | 'right'; + /** + * Выравнивает контент по вертикали. + */ + alignY?: 'top' | 'center' | 'bottom'; + /** + * Спрячет компонент через fade-out анимацию. + */ closing?: boolean; } @@ -54,11 +69,6 @@ export const PopoutWrapper = ({ !opened && animationFinishFallback.set(); }, [animationFinishFallback, opened]); - const { window } = useDOM(); - useGlobalEventListener(window, 'touchmove', (e) => e.preventDefault(), { - passive: false, - }); - return ( Скорей всего вам будет достаточно передать свой компонент в свойство `popout` компонента [`SplitLayout`](#/SplitLayout), +> который уже оборачивает в `PopoutWrapper`. +> +> Компоненты [`Alert`](#/Alert), [`ActionSheet`](#/ActionSheet) +> и [`ScreenSpinner`](#/ScreenSpinner) также уже используют этот компонент. +> +> Если всё же есть потребность использовать компонент отдельно, то ориентируетесь на пример ниже. + +Компонент-обертка для отрисовки всплывающих окон с затемнением фона. ```jsx static -import { PopoutWrapper } from '@vkontakte/vkui'; +import { PopoutWrapper, useScrollLock } from '@vkontakte/vkui'; - - Some content -; -``` +const App = () => { + const [opened, setOpened] = React.useState(false); + const popout = opened ? Some content : null; -Все всплывающие окна передаются в свойство `popout` компонента [`SplitLayout`](https://vkcom.github.io/VKUI/#/SplitLayout). + // Для улучшения UX, при открытии модального окна, стоит блокировать скролл страницы. + // Иначе у пользователя может быть два скролла: один на модальном окне, второй за ним. + useScrollLock(opened); + + return ( +
+

My Awesome App

+ + setOpened(event.checked)} /> + {popout} +
+ ); +}; +``` diff --git a/styleguide/pages/migration_v6.md b/styleguide/pages/migration_v6.md index 2b58866954..f4ea535531 100644 --- a/styleguide/pages/migration_v6.md +++ b/styleguide/pages/migration_v6.md @@ -559,6 +559,12 @@ npx @vkontakte/vkui-codemods --help
+### [`PopoutWrapper`](#/PopoutWrapper) + +- Теперь, при использовании компонента напрямую, необходимо самостоятельно импортировать и вызывать `useScrollLock()` (см. [issue #4314](https://github.com/VKCOM/VKUI/issues/4314)). + +
+ ### [`PullToRefresh`](#/PullToRefresh) - До этого `runTapticImpactOccurred()` вызывался внутри компонента после вызова обработчика `onRefresh`.