diff --git a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-dark-1-snap.png b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-dark-1-snap.png index 9e2a1ec164c..de135c0b97e 100644 --- a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-dark-1-snap.png +++ b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1630834b67d8b63be4828bf8d79c4df93d96858bd94280607abf8a9886e3585f -size 22393 +oid sha256:41b576516c188c5ddc331c14141a3586786a2300cb4de29eda8c41b6d4074022 +size 24297 diff --git a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-light-1-snap.png b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-light-1-snap.png index 3d7391bd6f2..80c9ebb586e 100644 --- a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-light-1-snap.png +++ b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-android-chromium-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:973b6115366ba0c9943f5686281780de1d9ce0d83b592cf04d7cc08da60287de -size 20396 +oid sha256:dec42dd997bc254a9560ee0907e29048f37e123d93953e54cb01c6e5d17037c5 +size 22325 diff --git a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-dark-1-snap.png b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-dark-1-snap.png index 728f04831fd..c92bf00d346 100644 --- a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-dark-1-snap.png +++ b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e94df940bea52e8a52c898c8bdf1baf7c3d08e81e7e7daf2a08fa70af4771c00 -size 34865 +oid sha256:b506989f80f8730136c4ddbddfdb4d097b8ff0f25d9e830ca893eb4d9601618b +size 37436 diff --git a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-light-1-snap.png b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-light-1-snap.png index 87dc01a744a..c1f4588edf4 100644 --- a/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-light-1-snap.png +++ b/packages/vkui/src/components/CardScroll/__image_snapshots__/cardscroll-ios-webkit-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b50fe80ebd4d04f2986328d2423dfee7ee2a764ea453e44d7c4f671aa73b97ff -size 32798 +oid sha256:6fb2ec608f0b3f7657eaf558383ca1ae7d06e577a80e38adba30f61448d430cd +size 35573 diff --git a/packages/vkui/src/components/CarouselBase/CarouselBase.module.css b/packages/vkui/src/components/CarouselBase/CarouselBase.module.css index c5680a397ec..09aa281cf6d 100644 --- a/packages/vkui/src/components/CarouselBase/CarouselBase.module.css +++ b/packages/vkui/src/components/CarouselBase/CarouselBase.module.css @@ -101,7 +101,7 @@ padding-inline-start: var(--vkui--size_base_padding_horizontal--regular); } -.host:hover .arrow { +.hover .arrow { opacity: var(--vkui--opacity_disable_accessibility); } diff --git a/packages/vkui/src/components/CarouselBase/CarouselBase.tsx b/packages/vkui/src/components/CarouselBase/CarouselBase.tsx index 569ea8a22c9..3ef14b09124 100644 --- a/packages/vkui/src/components/CarouselBase/CarouselBase.tsx +++ b/packages/vkui/src/components/CarouselBase/CarouselBase.tsx @@ -2,14 +2,15 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; -import { useAdaptivityHasPointer } from '../../hooks/useAdaptivityHasPointer'; import { useConfigDirection } from '../../hooks/useConfigDirection'; import { useExternRef } from '../../hooks/useExternRef'; import { useMutationObserver } from '../../hooks/useMutationObserver'; import { useResizeObserver } from '../../hooks/useResizeObserver'; import { useDOM } from '../../lib/dom'; +import { mergeCalls } from '../../lib/mergeCalls'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { warnOnce } from '../../lib/warnOnce'; +import { useHover } from '../Clickable/useState'; import { RootComponent } from '../RootComponent/RootComponent'; import { type CustomTouchEvent } from '../Touch/Touch'; import { Bullets } from './Bullets'; @@ -57,6 +58,8 @@ export const CarouselBase = ({ onChange, onPrevClick, onNextClick, + onPointerEnter, + onPointerLeave, align = 'left', showArrows, getRef, @@ -102,8 +105,6 @@ export const CarouselBase = ({ const [controlElementsState, setControlElementsState] = React.useState(CONTROL_ELEMENTS_STATE); - const hasPointer = useAdaptivityHasPointer(); - const slidesContainerId = React.useId(); const isCenterAlign = align === 'center'; @@ -550,21 +551,26 @@ export const CarouselBase = ({ } }; + const { isHovered, ...hoverHandlers } = useHover(); + + const handlers = mergeCalls(hoverHandlers, { onPointerEnter, onPointerLeave }); + return ( , ScrollArrowsTestIds { - hasPointer?: boolean; canSlideLeft: boolean; canSlideRight: boolean; onSlideLeft: (e: React.MouseEvent) => void; @@ -52,7 +51,6 @@ interface ScrollArrowsProps } export const ScrollArrows = ({ - hasPointer, canSlideLeft, canSlideRight, onSlideRight, @@ -69,7 +67,7 @@ export const ScrollArrows = ({ const { focusVisible: prevArrowFocusVisible, ...prevArrowFocusHandlers } = useFocusVisible(); const { focusVisible: nextArrowFocusVisible, ...nextArrowFocusHandlers } = useFocusVisible(); - return showArrows && hasPointer ? ( + return showArrows ? ( <> {canSlideLeft && ( { /** * Блокирование активации состояний. */ - lockState: boolean; - setParentStateLock: (v: boolean) => void; + lockState?: boolean; + setParentStateLock?: (v: boolean) => void; } /** * Управляет наведением на компонент, игнорирует тач события. */ -function useHover({ hovered, hasHover = true, lockState, setParentStateLock }: UseHoverProps) { +export function useHover({ + hovered, + hasHover = true, + lockState = false, + setParentStateLock = noop, +}: UseHoverProps = {}) { const [hoveredStateLocal, setHoveredStateLocal] = React.useState(false); const prevIsHoveredRef = React.useRef(undefined); diff --git a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.e2e-playground.tsx b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.e2e-playground.tsx index 583afadc0ab..9f19ec05f2c 100644 --- a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.e2e-playground.tsx +++ b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.e2e-playground.tsx @@ -64,38 +64,13 @@ export const HorizontalScrollSmallTabletPlayground = (props: ComponentPlayground ); }; -export const HorizontalScrollWithHasMousePlayground = ({ +export const HorizontalScrollHoverTestPlayground = ({ colorScheme, ...restProps }: ComponentPlaygroundProps) => { return ( - - - { - if (!element) { - return; - } - element.scrollLeft = 32; - }} - > - {items} - - - - - ); -}; - -export const HorizontalScrollWithoutHasMousePlayground = ({ - colorScheme, - ...restProps -}: ComponentPlaygroundProps) => { - return ( - - + { @@ -26,24 +24,6 @@ test.describe('HorizontalScroll', () => { }); }); -test.describe('HorizontalScroll', () => { - test.use({ - onlyForPlatforms: ['android'], - adaptivityProviderProps: { - viewWidth: ViewWidth.MOBILE, - hasPointer: false, - }, - }); - test('ViewWidth.MOBILE hasPointer=false', async ({ - mount, - expectScreenshotClippedToContent, - componentPlaygroundProps, - }) => { - await mount(); - await expectScreenshotClippedToContent(); - }); -}); - test.describe('HorizontalScroll', () => { const DATA_TESTID = 'horizontal-scroll'; const CUSTOM_ROOT_SELECTOR = `[data-testid="${DATA_TESTID}"]`; @@ -55,27 +35,7 @@ test.describe('HorizontalScroll', () => { componentPlaygroundProps, }) => { await mount( - , - ); - - await page.hover(CUSTOM_ROOT_SELECTOR); - - await expectScreenshotClippedToContent({ - cropToContentSelector: CUSTOM_ROOT_SELECTOR, - }); - }); - - test('does not have arrows without mouse', async ({ - mount, - page, - expectScreenshotClippedToContent, - componentPlaygroundProps, - }) => { - await mount( - , diff --git a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.module.css b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.module.css index 7182bf5b801..eb4245a09f4 100644 --- a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.module.css +++ b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.module.css @@ -43,8 +43,7 @@ opacity: 0; } -.host:hover .arrow, -.withConstArrows .arrow { +.showArrows .arrow { opacity: var(--vkui--opacity_disable_accessibility); } diff --git a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.test.tsx b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.test.tsx index 1f3cbd7bdb6..8b5a0f29716 100644 --- a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.test.tsx +++ b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.test.tsx @@ -3,7 +3,6 @@ import { act } from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { noop } from '@vkontakte/vkjs'; import { baselineComponent, userEvent, withFakeTimers } from '../../testing/utils'; -import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; import { DirectionProvider } from '../DirectionProvider/DirectionProvider'; import { HorizontalScroll } from './HorizontalScroll'; @@ -74,25 +73,23 @@ describe('HorizontalScroll', () => { let scrollContainer: HTMLDivElement | null = null; const result = render( - - { - scrollContainer = e; - mockRef(e); - }} - data-testid="horizontal-scroll" - showArrows="always" + { + scrollContainer = e; + mockRef(e); + }} + data-testid="horizontal-scroll" + showArrows="always" + > + - - , + Button + + , ); expect(document.activeElement).toBe(document.body); diff --git a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.tsx b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.tsx index fe7ba478122..1ac2f86c20c 100644 --- a/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.tsx +++ b/packages/vkui/src/components/HorizontalScroll/HorizontalScroll.tsx @@ -2,14 +2,15 @@ import * as React from 'react'; import { classNames, noop } from '@vkontakte/vkjs'; -import { useAdaptivityHasPointer } from '../../hooks/useAdaptivityHasPointer'; import { useConfigDirection } from '../../hooks/useConfigDirection'; import { useExternRef } from '../../hooks/useExternRef'; import { useFocusVisible } from '../../hooks/useFocusVisible'; import { useFocusVisibleClassName } from '../../hooks/useFocusVisibleClassName'; import { easeInOutSine } from '../../lib/fx'; +import { mergeCalls } from '../../lib/mergeCalls'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import type { HasRef, HTMLAttributesWithRootRef } from '../../types'; +import { useHover } from '../Clickable/useState'; import { RootComponent } from '../RootComponent/RootComponent'; import { ScrollArrow, type ScrollArrowProps } from '../ScrollArrow/ScrollArrow'; import styles from './HorizontalScroll.module.css'; @@ -219,6 +220,9 @@ export const HorizontalScroll = ({ contentWrapperRef, contentWrapperClassName, withPadding, + onPointerEnter, + onPointerLeave, + onMouseEnter, ...restProps }: HorizontalScrollProps): React.ReactNode => { const [canScrollStart, setCanScrollStart] = React.useState(false); @@ -237,7 +241,7 @@ export const HorizontalScroll = ({ const animationQueue = React.useRef([]); - const hasPointer = useAdaptivityHasPointer(); + const { isHovered, ...hoverHandlers } = useHover(); const scrollTo = React.useCallback( (getScrollPosition: ScrollPositionHandler) => { @@ -276,7 +280,7 @@ export const HorizontalScroll = ({ }, [getScrollToRight, scrollTo, scrollerRef]); const calculateArrowsVisibility = React.useCallback(() => { - if (showArrows && hasPointer && scrollerRef.current && !isCustomScrollingRef.current) { + if (showArrows && scrollerRef.current && !isCustomScrollingRef.current) { const scrollElement = scrollerRef.current; const scrollLeft = scrollElement.scrollLeft; @@ -286,7 +290,7 @@ export const HorizontalScroll = ({ scrollElement.scrollWidth, ); } - }, [showArrows, hasPointer, scrollerRef, isRtl]); + }, [showArrows, scrollerRef, isRtl]); React.useEffect(calculateArrowsVisibility, [calculateArrowsVisibility, children]); @@ -323,19 +327,23 @@ export const HorizontalScroll = ({ [scrollOnAnyWheel, calculateArrowsVisibility, scrollerRef], ); + const handlers = mergeCalls(hoverHandlers, { onPointerEnter, onPointerLeave }); + return ( - {showArrows && (hasPointer || hasPointer === undefined) && canScrollStart && ( + {showArrows && canScrollStart && ( )} - {showArrows && (hasPointer || hasPointer === undefined) && canScrollEnd && ( + {showArrows && canScrollEnd && ( { - it('returns on client', () => { - const { result } = renderHook(useAdaptivityHasPointer, {}); - expect(result.current).toEqual(true); - }); - - it('context hasPointer={true}', () => { - const { result } = renderHook(useAdaptivityHasPointer, { - wrapper: createWrapper(AdaptivityProvider, { hasPointer: true }), - }); - expect(result.current).toEqual(true); - }); - - it('context hasPointer={false}', () => { - const { result } = renderHook(useAdaptivityHasPointer, { - wrapper: createWrapper(AdaptivityProvider, { hasPointer: false }), - }); - expect(result.current).toEqual(false); - }); -}); diff --git a/packages/vkui/src/hooks/useAdaptivityHasPointer.ts b/packages/vkui/src/hooks/useAdaptivityHasPointer.ts deleted file mode 100644 index 532f22eb9b0..00000000000 --- a/packages/vkui/src/hooks/useAdaptivityHasPointer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import { hasMouse as hasPointerLib } from '@vkontakte/vkjs'; -import { AdaptivityContext } from '../components/AdaptivityProvider/AdaptivityContext'; -import { useIsClient } from './useIsClient'; - -/** - * Определение происходит с помощью `window.matchMedia`. Для того, чтобы не было ошибок при гидратации, по умолчанию - * откладываем определение на второй рендер. - * - * [No SSR] Если передать `false`, то определение будет сразу. - */ -export function useAdaptivityHasPointer(deferDetect?: true): undefined | boolean; -export function useAdaptivityHasPointer(deferDetect?: false): boolean; -export function useAdaptivityHasPointer(deferDetect = true): undefined | boolean { - const { hasPointer: hasPointerContext } = React.useContext(AdaptivityContext); - - const needTwoPassRendering = deferDetect || hasPointerContext === undefined; - - const isClient = useIsClient(!needTwoPassRendering); - if (!isClient || hasPointerContext !== undefined) { - return hasPointerContext; - } - - return hasPointerLib; -} diff --git a/website/content/components/gallery.mdx b/website/content/components/gallery.mdx index d8fe74d6812..36c805e7f3c 100644 --- a/website/content/components/gallery.mdx +++ b/website/content/components/gallery.mdx @@ -61,8 +61,7 @@ import { BlockWrapper } from '@/components/wrappers'; - `arrowAreaHeight` - размер активной зоны стрелок. Определяет область вокруг стрелок, реагирующую на взаимодействие пользователя: - `stretch` - на всю высоту галереи - `fit` - фиксированная высота -- `showArrows` - флаг, определяющий нужно ли показывать стрелки. Видимость стрелок также зависит - от параметра адаптивности `hasPointer`. Например, на мобильных устройствах стрелки отображаться никогда не будут. +- `showArrows` - флаг, определяющий нужно ли показывать стрелки при наведении. ```jsx diff --git a/website/content/components/horizontal-scroll.mdx b/website/content/components/horizontal-scroll.mdx index 646e5ba35c4..8cdb499a071 100644 --- a/website/content/components/horizontal-scroll.mdx +++ b/website/content/components/horizontal-scroll.mdx @@ -81,9 +81,6 @@ function handleScrollToRight(currentPosition) { Задаётся свойством `showArrows`. -> На устройствах с [`hasPointer={false}`](/components/adaptivity-provider) (чаще всего, мобильные устройства), -> стрелки всегда скрыты, вне зависимости от значения `showArrows`. - - `true` — стрелки показываются при наведении на компонент (по умолчанию); - `false` — стрелки скрыты; - `"always"` — стрелки всегда видны.