From 7bc80e01b871ac04d2d8a47ff6df8977b379ea81 Mon Sep 17 00:00:00 2001 From: hextion <100ishundred@gmail.com> Date: Thu, 11 Jul 2024 00:18:50 +0300 Subject: [PATCH] feat(bottom-sheet): added virtual keyboard support --- .changeset/ten-roses-smell.md | 5 +++ packages/bottom-sheet/src/component.tsx | 15 +++++-- packages/bottom-sheet/src/hooks/index.ts | 1 + .../src/hooks/use-visualviewport-size.ts | 39 +++++++++++++++++++ packages/bottom-sheet/src/types.ts | 5 +++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 .changeset/ten-roses-smell.md create mode 100644 packages/bottom-sheet/src/hooks/index.ts create mode 100644 packages/bottom-sheet/src/hooks/use-visualviewport-size.ts diff --git a/.changeset/ten-roses-smell.md b/.changeset/ten-roses-smell.md new file mode 100644 index 0000000000..2a3d432993 --- /dev/null +++ b/.changeset/ten-roses-smell.md @@ -0,0 +1,5 @@ +--- +'@alfalab/core-components-bottom-sheet': minor +--- + +Добавлен проп для случаев, когда необходима отзывчивость компонента из-за изменении видимой части браузера при открытии клавиатуры устройства diff --git a/packages/bottom-sheet/src/component.tsx b/packages/bottom-sheet/src/component.tsx index f3349515b5..5f715e3c4b 100644 --- a/packages/bottom-sheet/src/component.tsx +++ b/packages/bottom-sheet/src/component.tsx @@ -21,6 +21,7 @@ import { Header, HeaderProps } from './components/header/Component'; import { SwipeableBackdrop } from './components/swipeable-backdrop/Component'; import { horizontalDirections } from './consts/swipeConsts'; import { ShouldSkipSwipingParams } from './types/swipeTypes'; +import { useVisibleViewportSize } from './hooks'; import type { BottomSheetProps } from './types'; import { CLOSE_OFFSET, @@ -103,10 +104,14 @@ export const BottomSheet = forwardRef( swipeableMarkerClassName, backButtonProps, iOSLock = false, + virtualKeyboard = false, }, ref, ) => { - const fullHeight = use100vh() || 0; + const windowHeight = use100vh() ?? 0; + const visibleViewportSize = useVisibleViewportSize(virtualKeyboard); + const fullHeight = virtualKeyboard ? visibleViewportSize?.height ?? 0 : windowHeight; + // Хук use100vh рассчитывает высоту вьюпорта в useEffect, поэтому на первый рендер всегда возвращает null. const isFirstRender = fullHeight === 0; @@ -123,10 +128,10 @@ export const BottomSheet = forwardRef( ? document?.documentElement?.clientHeight || window?.innerHeight : 0; - const viewHeight = os.isIOS() ? iOSViewHeight : fullHeight; + const viewHeight = os.isIOS() && !virtualKeyboard ? iOSViewHeight : fullHeight; return [0, viewHeight - headerOffset]; - }, [fullHeight, headerOffset, magneticAreasProp]); + }, [fullHeight, headerOffset, magneticAreasProp, virtualKeyboard]); const lastMagneticArea = magneticAreas[magneticAreas.length - 1]; @@ -544,6 +549,10 @@ export const BottomSheet = forwardRef( ? `${lastMagneticArea}px` : 'unset', maxHeight: isFirstRender ? 0 : `${lastMagneticArea}px`, + marginBottom: + virtualKeyboard && visibleViewportSize && windowHeight > visibleViewportSize.height + ? windowHeight - visibleViewportSize.height - visibleViewportSize.offsetTop + : undefined, }); const renderMarker = () => { diff --git a/packages/bottom-sheet/src/hooks/index.ts b/packages/bottom-sheet/src/hooks/index.ts new file mode 100644 index 0000000000..919b9000b4 --- /dev/null +++ b/packages/bottom-sheet/src/hooks/index.ts @@ -0,0 +1 @@ +export { useVisibleViewportSize } from './use-visualviewport-size'; diff --git a/packages/bottom-sheet/src/hooks/use-visualviewport-size.ts b/packages/bottom-sheet/src/hooks/use-visualviewport-size.ts new file mode 100644 index 0000000000..4d2e7725fa --- /dev/null +++ b/packages/bottom-sheet/src/hooks/use-visualviewport-size.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; + +import { fnUtils, isClient } from '@alfalab/core-components-shared'; + +type VisualViewportSize = Pick; + +const measureVisualViewport = ( + visualViewport: VisualViewport | null, +): VisualViewportSize | null => { + if (!visualViewport) return null; + const { height, offsetTop } = visualViewport; + + return { height, offsetTop }; +}; + +export function useVisibleViewportSize(enabled = false): VisualViewportSize | null { + const [size, setSize] = useState | null>(() => + isClient() ? measureVisualViewport(window.visualViewport) : null, + ); + + useEffect(() => { + const { visualViewport } = window; + + if (!isClient() || !enabled || !visualViewport) return fnUtils.noop; + + const listener = (event: Event) => + setSize(measureVisualViewport(event.target as VisualViewport)); + + visualViewport.addEventListener('resize', listener); + visualViewport.addEventListener('scroll', listener); + + return () => { + visualViewport.removeEventListener('resize', listener); + visualViewport.removeEventListener('scroll', listener); + }; + }, [enabled]); + + return size; +} diff --git a/packages/bottom-sheet/src/types.ts b/packages/bottom-sheet/src/types.ts index 873b99cdf7..334f9c5624 100644 --- a/packages/bottom-sheet/src/types.ts +++ b/packages/bottom-sheet/src/types.ts @@ -347,4 +347,9 @@ export type BottomSheetProps = { * Блокирует скролл когда модальное окно открыто. Работает только на iOS */ iOSLock?: boolean; + + /** + * Учитывать высоту виртуальной клавиатуры + */ + virtualKeyboard?: boolean; };