From 887a298a0b4d1e34a4f356bf27929662f133fae2 Mon Sep 17 00:00:00 2001 From: Inomdzhon Mirdzhamolov Date: Mon, 4 Dec 2023 23:20:38 +0300 Subject: [PATCH 1/3] refactor(floating): mv all floating components to same API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit h2. ~~`Tooltip`~~ -> [OnboardingTooltip](#/OnboardingTooltip) ```diff
Target
``` h2. ~~`TextTooltip`~~ -> [Tooltip](#/Tooltip) ```diff
Target
``` h1. ⚠️ Поправить в migration_v6.md h2. [Popper](#/Popper) ```diff {}} + onPlacementChange={(placement) => {}} - forcePortal + usePortal - portalRoot={someHTMLElement} + usePortal={someHTMLElement} />
Target
``` - `onPlacementChange` теперь вызывается только в случае, если `Popper` подобрал оптимальный `placement` вместо пользовательского. - `renderContent` удалён в пользу `children`. Раньше из `renderContent` можно было получить `className`, который задаёт `Popper`, сейчас этот `className` пустой. - `targetRef` теперь умеет принимать `VirtualElement`. - `arrowProps` принимает атрибуты `HTMLDivElement`, а также `iconStyle` и `iconClassName`. --- .../ActionSheet/ActionSheetDropdownMenu.tsx | 2 +- .../vkui/src/components/ActionSheet/types.ts | 2 +- .../src/components/AppRoot/AppRootPortal.tsx | 77 ++-- .../CustomSelectDropdown.tsx | 2 +- .../components/FixedLayout/FixedLayout.tsx | 6 +- .../components/HoverPopper/HoverPopper.tsx | 102 ----- .../OnboardingTooltip.e2e-playground.tsx} | 31 +- .../OnboardingTooltip.e2e.tsx | 12 + .../OnboardingTooltip.module.css} | 2 +- .../OnboardingTooltip.stories.tsx | 286 ++++++++++++ .../OnboardingTooltip.test.tsx | 106 +++++ .../OnboardingTooltip/OnboardingTooltip.tsx | 221 +++++++++ .../OnboardingTooltipContainer.tsx | 19 + .../components/OnboardingTooltip/Readme.md | 318 +++++++++++++ ...ngtooltip-android-chromium-dark-1-snap.png | 3 + ...gtooltip-android-chromium-light-1-snap.png | 3 + ...boardingtooltip-ios-webkit-dark-1-snap.png | 3 + ...oardingtooltip-ios-webkit-light-1-snap.png | 3 + ...dingtooltip-vkcom-chromium-dark-1-snap.png | 3 + ...ingtooltip-vkcom-chromium-light-1-snap.png | 3 + ...rdingtooltip-vkcom-firefox-dark-1-snap.png | 3 + ...dingtooltip-vkcom-firefox-light-1-snap.png | 3 + ...ardingtooltip-vkcom-webkit-dark-1-snap.png | 3 + ...rdingtooltip-vkcom-webkit-light-1-snap.png | 3 + packages/vkui/src/components/Panel/Panel.tsx | 4 +- .../components/PanelHeader/PanelHeader.tsx | 6 +- .../vkui/src/components/Popover/Popover.tsx | 152 +++--- .../src/components/Popper/Popper.stories.tsx | 2 +- .../vkui/src/components/Popper/Popper.tsx | 61 +-- .../components/PopperArrow/PopperArrow.tsx | 7 +- .../Slider/SliderThumb/SliderThumb.tsx | 10 +- .../vkui/src/components/TextTooltip/Readme.md | 11 - .../TextTooltip/TextTooltip.module.css | 59 --- .../TextTooltip/TextTooltip.stories.tsx | 26 -- .../components/TextTooltip/TextTooltip.tsx | 61 --- .../vkui/src/components/Tooltip/Readme.md | 320 +------------ .../src/components/Tooltip/Tooltip.e2e.tsx | 8 - .../components/Tooltip/Tooltip.stories.tsx | 272 +---------- .../src/components/Tooltip/Tooltip.test.tsx | 105 +---- .../vkui/src/components/Tooltip/Tooltip.tsx | 433 +++++++----------- .../components/Tooltip/TooltipContainer.tsx | 10 - .../tooltip-android-chromium-dark-1-snap.png | 3 - .../tooltip-android-chromium-light-1-snap.png | 3 - .../tooltip-ios-webkit-dark-1-snap.png | 3 - .../tooltip-ios-webkit-light-1-snap.png | 3 - .../tooltip-vkcom-chromium-dark-1-snap.png | 3 - .../tooltip-vkcom-chromium-light-1-snap.png | 3 - .../tooltip-vkcom-firefox-dark-1-snap.png | 3 - .../tooltip-vkcom-firefox-light-1-snap.png | 3 - .../tooltip-vkcom-webkit-dark-1-snap.png | 3 - .../tooltip-vkcom-webkit-light-1-snap.png | 3 - .../TooltipBase/TooltipBase.module.css | 5 - .../components/TooltipBase/TooltipBase.tsx | 56 +-- packages/vkui/src/index.ts | 11 +- packages/vkui/src/lib/createPortal.ts | 4 +- packages/vkui/src/lib/floating/functions.ts | 14 +- packages/vkui/src/lib/floating/index.ts | 5 +- .../floating/{types.ts => types/common.ts} | 0 .../vkui/src/lib/floating/types/component.ts | 55 +++ .../useFloatingMiddlewaresBootstrap/index.ts | 4 +- .../useFloatingWithInteractions.test.tsx.snap | 5 + .../useFloatingWithInteractions/types.ts | 21 +- .../useFloatingWithInteractions.ts | 43 +- .../ComplexType/ComplexTypeRenderer.js | 11 +- styleguide/Components/Setting/Setting.js | 10 +- styleguide/config.js | 2 +- 66 files changed, 1512 insertions(+), 1527 deletions(-) delete mode 100644 packages/vkui/src/components/HoverPopper/HoverPopper.tsx rename packages/vkui/src/components/{Tooltip/Tooltip.e2e-playground.tsx => OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx} (50%) create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e.tsx rename packages/vkui/src/components/{Tooltip/Tooltip.module.css => OnboardingTooltip/OnboardingTooltip.module.css} (78%) create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.test.tsx create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipContainer.tsx create mode 100644 packages/vkui/src/components/OnboardingTooltip/Readme.md create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png create mode 100644 packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png delete mode 100644 packages/vkui/src/components/TextTooltip/Readme.md delete mode 100644 packages/vkui/src/components/TextTooltip/TextTooltip.module.css delete mode 100644 packages/vkui/src/components/TextTooltip/TextTooltip.stories.tsx delete mode 100644 packages/vkui/src/components/TextTooltip/TextTooltip.tsx delete mode 100644 packages/vkui/src/components/Tooltip/Tooltip.e2e.tsx delete mode 100644 packages/vkui/src/components/Tooltip/TooltipContainer.tsx delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-dark-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-light-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-dark-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-light-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-dark-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-light-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-dark-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-light-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-dark-1-snap.png delete mode 100644 packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-light-1-snap.png rename packages/vkui/src/lib/floating/{types.ts => types/common.ts} (100%) create mode 100644 packages/vkui/src/lib/floating/types/component.ts diff --git a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx index a699577088..c2c957ee20 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx +++ b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx @@ -77,7 +77,7 @@ export const ActionSheetDropdownMenu = ({ )} style={style} getRootRef={elementRef} - forcePortal={false} + usePortal={false} > {children} diff --git a/packages/vkui/src/components/ActionSheet/types.ts b/packages/vkui/src/components/ActionSheet/types.ts index d55cf7c895..dc38eb828f 100644 --- a/packages/vkui/src/components/ActionSheet/types.ts +++ b/packages/vkui/src/components/ActionSheet/types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import type { PlacementWithAuto } from '../../lib/floating/types'; +import type { PlacementWithAuto } from '../../lib/floating/types/common'; import { FocusTrapProps } from '../FocusTrap/FocusTrap'; export type ToggleRef = Element | null | undefined | React.RefObject; diff --git a/packages/vkui/src/components/AppRoot/AppRootPortal.tsx b/packages/vkui/src/components/AppRoot/AppRootPortal.tsx index d198d05a2e..d0d3f10f20 100644 --- a/packages/vkui/src/components/AppRoot/AppRootPortal.tsx +++ b/packages/vkui/src/components/AppRoot/AppRootPortal.tsx @@ -1,29 +1,21 @@ import * as React from 'react'; -import { createPortal } from 'react-dom'; import { useAppearance } from '../../hooks/useAppearance'; import { useIsClient } from '../../hooks/useIsClient'; +import { createPortal } from '../../lib/createPortal'; import { isRefObject } from '../../lib/isRefObject'; import { HasChildren } from '../../types'; import { AppearanceProvider } from '../AppearanceProvider/AppearanceProvider'; -import { AppRootContext } from './AppRootContext'; +import { AppRootContext, type AppRootContextInterface } from './AppRootContext'; export interface AppRootPortalProps extends HasChildren { - className?: string; - forcePortal?: boolean; /** - * Кастомный root-элемент портала. - * При передаче вместе с `forcePorta=true` игнорируется `portalRoot` и `disablePortal` - * из контекста `AppRoot`. + * - При передаче `true` будет использовать `portalRoot` из контекста `AppRoot`. + * - При передаче элемента будут игнорироваться `portalRoot` и `disablePortal` из контекста `AppRoot`. */ - portalRoot?: HTMLElement | React.RefObject | null; + usePortal?: boolean | HTMLElement | React.RefObject | null; } -export const AppRootPortal = ({ - children, - className, - forcePortal: forcePortalProp, - portalRoot: portalRootProp = null, -}: AppRootPortalProps) => { +export const AppRootPortal = ({ children, usePortal }: AppRootPortalProps) => { const { portalRoot, mode, disablePortal } = React.useContext(AppRootContext); const appearance = useAppearance(); @@ -32,40 +24,39 @@ export const AppRootPortal = ({ return null; } - const forcePortal = forcePortalProp ?? mode !== 'full'; - - const portalContainer = getPortalContainer(portalRootProp, portalRoot); - - const ignoreDisablePortalFlagFromContext = portalRootProp && forcePortal; - const shouldUsePortal = ignoreDisablePortalFlagFromContext - ? true - : !disablePortal && portalContainer && forcePortal; + const portalContainer = resolvePortalContainer(usePortal, portalRoot); + if (!portalContainer || shouldDisablePortal(usePortal, mode, Boolean(disablePortal))) { + return children; + } - return shouldUsePortal && portalContainer ? ( - createPortal( - -
{children}
-
, - portalContainer, - ) - ) : ( - {children} + return createPortal( + {children}, + portalContainer, ); }; -/** - * Получает из кастомного пропа `partialRootProp` и `partialRoot` контекста - * контейнер-элемент для портала. - * `partialRootProp` может быть ref элементом. - * - */ -function getPortalContainer( - portalRootProp?: HTMLElement | React.RefObject | null, - portalRoot?: HTMLElement | null, +function shouldDisablePortal( + usePortal: AppRootPortalProps['usePortal'], + mode: AppRootContextInterface['mode'], + disablePortal: boolean, +) { + if (usePortal !== undefined) { + if (typeof usePortal !== 'boolean') { + return false; + } + return disablePortal || usePortal !== true; + } + // fallback + return disablePortal || mode !== 'full'; +} + +function resolvePortalContainer( + usePortal: AppRootPortalProps['usePortal'], + portalRootFromContext: PortalRootFromContext, ) { - if (!portalRootProp) { - return portalRoot; + if (usePortal === true || !usePortal) { + return portalRootFromContext ? portalRootFromContext : null; } - return isRefObject(portalRootProp) ? portalRootProp.current : portalRootProp; + return isRefObject(usePortal) ? usePortal.current : usePortal; } diff --git a/packages/vkui/src/components/CustomSelectDropdown/CustomSelectDropdown.tsx b/packages/vkui/src/components/CustomSelectDropdown/CustomSelectDropdown.tsx index eeb9c8d913..7b58491f99 100644 --- a/packages/vkui/src/components/CustomSelectDropdown/CustomSelectDropdown.tsx +++ b/packages/vkui/src/components/CustomSelectDropdown/CustomSelectDropdown.tsx @@ -74,7 +74,7 @@ export const CustomSelectDropdown = ({ ), className, )} - forcePortal={forcePortal} + usePortal={forcePortal} autoUpdateOnTargetResize {...restProps} > diff --git a/packages/vkui/src/components/FixedLayout/FixedLayout.tsx b/packages/vkui/src/components/FixedLayout/FixedLayout.tsx index 35abaec250..d005a637c7 100644 --- a/packages/vkui/src/components/FixedLayout/FixedLayout.tsx +++ b/packages/vkui/src/components/FixedLayout/FixedLayout.tsx @@ -5,8 +5,8 @@ import { useGlobalEventListener } from '../../hooks/useGlobalEventListener'; import { usePlatform } from '../../hooks/usePlatform'; import { useDOM } from '../../lib/dom'; import { HTMLAttributesWithRootRef } from '../../types'; +import { OnboardingTooltipContainer } from '../OnboardingTooltip/OnboardingTooltipContainer'; import { SplitColContext } from '../SplitCol/SplitColContext'; -import { TooltipContainer } from '../Tooltip/TooltipContainer'; import styles from './FixedLayout.module.css'; const stylesVertical = { @@ -79,7 +79,7 @@ export const FixedLayout = ({ useGlobalEventListener(window, 'resize', doResize); return ( - {children} - + ); }; diff --git a/packages/vkui/src/components/HoverPopper/HoverPopper.tsx b/packages/vkui/src/components/HoverPopper/HoverPopper.tsx deleted file mode 100644 index 4f561cbbba..0000000000 --- a/packages/vkui/src/components/HoverPopper/HoverPopper.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import * as React from 'react'; -import { useEventListener } from '../../hooks/useEventListener'; -import { usePatchChildren } from '../../hooks/usePatchChildren'; -import { useTimeout } from '../../hooks/useTimeout'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; -import { Popper, PopperCommonProps } from '../Popper/Popper'; - -export interface HoverPopperProps extends Omit { - /** - * Содержимое тултипа - */ - content?: React.ReactNode; - /** - * Если передан, то тултип будет показан/скрыт в зависимости от значения свойства - */ - shown?: boolean; - /** - * Вызывается при каждом изменении видимости тултипа - */ - onShownChange?: (shown: boolean) => void; - /** - * Количество миллисекунд, после которых произойдет показ дропдауна - */ - showDelay?: number; - /** - * Количество миллисекунд, после которых произойдет скрытие дропдауна - */ - hideDelay?: number; - /** - * Либо jsx-элемент (div, button, etc.), либо компонент со свойством `getRootRef`, которое применяется к корневому элементу компонента - */ - children?: React.ReactElement; -} - -export const HoverPopper = ({ - getRootRef, - content, - children, - onShownChange, - shown: _shown, - showDelay = 150, - hideDelay = 150, - ...restProps -}: HoverPopperProps) => { - const [computedShown, setComputedShown] = React.useState(_shown || false); - - const shown = typeof _shown === 'boolean' ? _shown : computedShown; - - const setShown = (value: boolean) => { - if (typeof _shown !== 'boolean') { - setComputedShown(value); - } - typeof onShownChange === 'function' && onShownChange(value); - }; - - const showTimeout = useTimeout(() => { - setShown(true); - }, showDelay); - - const hideTimeout = useTimeout(() => { - setShown(false); - }, hideDelay); - - const [childRef, child] = usePatchChildren(children); - - const onTargetEnter = () => { - hideTimeout.clear(); - showTimeout.set(); - }; - - const onTargetLeave = () => { - showTimeout.clear(); - hideTimeout.set(); - }; - - const targetEnterListener = useEventListener('pointerenter', onTargetEnter); - const targetLeaveListener = useEventListener('pointerleave', onTargetLeave); - - useIsomorphicLayoutEffect(() => { - if (childRef.current) { - targetEnterListener.add(childRef.current); - targetLeaveListener.add(childRef.current); - } - }, []); - - return ( - - {child} - {shown && ( - - {content} - - )} - - ); -}; diff --git a/packages/vkui/src/components/Tooltip/Tooltip.e2e-playground.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx similarity index 50% rename from packages/vkui/src/components/Tooltip/Tooltip.e2e-playground.tsx rename to packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx index 622b126188..a3c7ed85f7 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.e2e-playground.tsx +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx @@ -4,10 +4,10 @@ import { type ComponentPlaygroundProps, TEST_CLASS_NAMES, } from '@vkui-e2e/playground-helpers'; -import { Tooltip, type TooltipProps } from './Tooltip'; -import { TooltipContainer } from './TooltipContainer'; +import { OnboardingTooltip, type OnboardingTooltipProps } from './OnboardingTooltip'; +import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; -export const TooltipPlayground = (props: ComponentPlaygroundProps) => { +export const OnboardingTooltipPlayground = (props: ComponentPlaygroundProps) => { return ( { header: [undefined, 'header'], }, { - alignX: ['left', 'right'], - alignY: ['top', 'bottom'], + placement: ['top-start', 'top-end', 'bottom-start', 'bottom-end'], }, { - alignX: ['left'], - alignY: ['top'], - cornerOffset: [5, -5], + placement: ['top-start'], + arrowCornerOffset: [5, -5], }, { - alignX: ['left'], - alignY: ['top'], - cornerAbsoluteOffset: [10, -1], + placement: ['top-start'], + arrowCornerAbsoluteOffset: [10, -1], }, ]} > - {(props: TooltipProps) => ( - ( + - +
Tooltip target
-
-
+ + )}
); diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e.tsx new file mode 100644 index 0000000000..5a58ec39c3 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { test } from '@vkui-e2e/test'; +import { OnboardingTooltipPlayground } from './OnboardingTooltip.e2e-playground'; + +test('OnboardingTooltip', async ({ + mount, + expectScreenshotClippedToContent, + componentPlaygroundProps, +}) => { + await mount(); + await expectScreenshotClippedToContent(); +}); diff --git a/packages/vkui/src/components/Tooltip/Tooltip.module.css b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.module.css similarity index 78% rename from packages/vkui/src/components/Tooltip/Tooltip.module.css rename to packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.module.css index 9a12483ab3..8740426aa0 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.module.css +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.module.css @@ -1,4 +1,4 @@ -.Tooltip__overlay { +.OnboardingTooltip__overlay { position: fixed; inset-inline-start: 0; inset-block-start: 0; diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx new file mode 100644 index 0000000000..c6c7405186 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx @@ -0,0 +1,286 @@ +import * as React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import { withVKUILayout } from '../../storybook/VKUIDecorators'; +import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants'; +import { Avatar } from '../Avatar/Avatar'; +import { Group } from '../Group/Group'; +import { List } from '../List/List'; +import { Panel } from '../Panel/Panel'; +import { PanelHeader } from '../PanelHeader/PanelHeader'; +import { PanelHeaderBack } from '../PanelHeaderBack/PanelHeaderBack'; +import { SimpleCell } from '../SimpleCell/SimpleCell'; +import { View } from '../View/View'; +import { OnboardingTooltip, OnboardingTooltipProps } from './OnboardingTooltip'; +import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; + +const story: Meta = { + title: 'Poppers/OnboardingTooltip', + component: OnboardingTooltip, + parameters: DisableCartesianParam, +}; + +export default story; + +type Story = StoryObj; + +export const Playground: Story = { + render: (args) => ( + +
+ + + +
+
+ ), + args: { + text: 'OnboardingTooltip', + }, +}; + +export const ShowCase: Story = { + render: function Render() { + const [tooltip, setTooltip] = React.useState(true); + const [tooltip2, setTooltip2] = React.useState(true); + const [tooltip3, setTooltip3] = React.useState(false); + const [activePanel, setActivePanel] = React.useState('tooltip'); + + return ( + + + Onboarding tooltip + + + Музыка + Видео + Игры + Закладки + Документы + Денежные переводы + + + + setTooltip(false)} + offsetByMainAxis={10} + > + setActivePanel('tooltip2')}>VK Pay + + + + + + { + setTooltip2(false); + setTooltip3(true); + }} + text="Нажмите на кнопку, если хотите вернуться" + header="Назад" + > + setActivePanel('tooltip')} /> + + } + > + OnboardingTooltip + + + + setTooltip3(false)} + arrowCornerOffset={-6} + > + + + } + subtitle="Веб-сайт" + > + Команда ВКонтакте + + } subtitle="Музыкант"> + Robbie Williams + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + + + + + ); + }, + decorators: [withVKUILayout], + parameters: CanvasFullLayout, +}; + +export const WithOnboardingTooltipContainer: Story = { + render: () => ( + <> + + +
+ +
+
+ +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ + +
+ +
+
+
+ + ), +}; + +export const CustomArrowIcon: Story = { + render: () => { + const ARROW_HEIGHT = 11; + + const CustomIcon = (props: React.SVGAttributes) => { + return ( + + + + ); + }; + + return ( + + +
+ Якорь +
+
+
+ ); + }, +}; diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.test.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.test.tsx new file mode 100644 index 0000000000..a8a840ed1c --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.test.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import { Fragment, HtmlHTMLAttributes, ReactElement } from 'react'; +import { render, screen } from '@testing-library/react'; +import { baselineComponent, waitForFloatingPosition } from '../../testing/utils'; +import { HasRootRef } from '../../types'; +import { OnboardingTooltip } from './OnboardingTooltip'; +import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; + +const renderTooltip = async (jsx: ReactElement) => { + render({jsx}); + await waitForFloatingPosition(); +}; +const RootRef = ({ + getRootRef, + ...props +}: HasRootRef & HtmlHTMLAttributes) => ( +
+); + +describe(OnboardingTooltip, () => { + baselineComponent( + (props) => ( + + +
+ + + ), + { forward: false, getRootRef: false }, + ); + + it('renders tooltip when shown=true', async () => { + await renderTooltip( + +
+ , + ); + expect(screen.getByText('text')).toBeTruthy(); + }); + + it('supports child with getRootRef', async () => { + await renderTooltip( + + + , + ); + expect(screen.getByText('text')).toBeTruthy(); + }); + + it('does not create extra markup when shown=false', () => { + render( + + +
+ + , + ); + expect(screen.queryByText('text')).toBeFalsy(); + const container = screen.getByTestId('container'); + expect(container.childElementCount).toBe(1); + expect(container.firstElementChild).toBe(screen.getByTestId('xxx')); + }); + + it('does not explode when children does not accept ref', () => { + expect(() => render()).not.toThrow(); + expect(() => + render({'text' as any}), + ).not.toThrow(); + expect(() => + render( + {[
,
] as any}, + ), + ).not.toThrow(); + expect(() => + render( + + + text +
+ + , + ), + ).not.toThrow(); + }); + + describe('preserves child ref', () => { + it('on DOM child', async () => { + const ref = jest.fn(); + await renderTooltip( + +
+ , + ); + expect(ref).toHaveBeenCalledWith(screen.getByTestId('xxx')); + }); + it('on VKUI child', async () => { + const ref = jest.fn(); + await renderTooltip( + + + , + ); + expect(ref).toHaveBeenCalledWith(screen.getByTestId('xxx')); + }); + }); +}); diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx new file mode 100644 index 0000000000..b7ea684102 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx @@ -0,0 +1,221 @@ +import * as React from 'react'; +import { hasReactNode } from '@vkontakte/vkjs'; +import { useExternRef } from '../../hooks/useExternRef'; +import { usePatchChildren } from '../../hooks/usePatchChildren'; +import { createPortal } from '../../lib/createPortal'; +import { + autoUpdateFloatingElement, + convertFloatingDataToReactCSSProperties, + type FloatingComponentProps, + type PlacementWithAuto, + useFloating, + type UseFloatingMiddleware, + useFloatingMiddlewaresBootstrap, +} from '../../lib/floating'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; +import { warnOnce } from '../../lib/warnOnce'; +import { useNavTransition } from '../NavTransitionContext/NavTransitionContext'; +import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING } from '../PopperArrow/DefaultIcon'; +import { TOOLTIP_MAX_WIDTH, TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase'; +import { onboardingTooltipContainerAttr } from './OnboardingTooltipContainer'; +import styles from './OnboardingTooltip.module.css'; + +const warn = warnOnce('OnboardingTooltip'); + +type AllowedFloatingComponentProps = Pick< + FloatingComponentProps, + | 'arrowHeight' + | 'arrowPadding' + | 'arrowRef' + | 'placement' + | 'offsetByMainAxis' + | 'offsetByCrossAxis' + | 'shown' + | 'children' +>; + +type AllowedTooltipBaseProps = Omit; + +export interface OnboardingTooltipProps + extends AllowedFloatingComponentProps, + AllowedTooltipBaseProps { + /** + * Сдвиг стрелочки относительно центра дочернего элемента. + */ + arrowCornerOffset?: number; + /** + * Сдвиг стрелочки относительно ширины тултипа + */ + arrowCornerAbsoluteOffset?: number; + /** + * Callback, который вызывается при клике по любому месту в пределах экрана. + */ + onClose?(this: void): void; +} + +/** + * @see https://vkcom.github.io/VKUI/#/Tooltip + */ +export const OnboardingTooltip = ({ + id: idProp, + children, + shown: shownProp = true, + arrowPadding = DEFAULT_ARROW_PADDING, + arrowHeight = DEFAULT_ARROW_HEIGHT, + offsetByMainAxis = 0, + offsetByCrossAxis = 0, + arrowCornerOffset = 0, + arrowCornerAbsoluteOffset, + onClose, + placement: placementProp = 'bottom-start', + maxWidth = TOOLTIP_MAX_WIDTH, + style: styleProp, + getRootRef, + ...restProps +}: OnboardingTooltipProps) => { + const generatedId = React.useId(); + const tooltipId = idProp || generatedId; + const { entering } = useNavTransition(); + + const [arrowRef, setArrowRef] = React.useState(null); + const [tooltipContainer, setTooltipContainer] = React.useState(null); + const [positionStrategy, setPositionStrategy] = React.useState<'fixed' | 'absolute'>('absolute'); + const shown = shownProp && tooltipContainer && !entering; + + const customMiddlewares = React.useMemo( + () => [getArrowOffsetMiddleware(arrowCornerOffset, arrowCornerAbsoluteOffset)], + [arrowCornerAbsoluteOffset, arrowCornerOffset], + ); + const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ + placement: placementProp, + offsetByMainAxis, + offsetByCrossAxis, + arrowRef, + arrow: true, + arrowHeight, + arrowPadding, + customMiddlewares, + }); + const { + x: floatingDataX, + y: floatingDataY, + refs, + placement: resolvedPlacement, + middlewareData: { arrow: arrowCoords }, + } = useFloating({ + strategy: positionStrategy, + placement: strictPlacement, + middleware: middlewares, + whileElementsMounted: autoUpdateFloatingElement, + }); + const tooltipRef = useExternRef(getRootRef, refs.setFloating); + const [childRef, child] = usePatchChildren(children, { + 'aria-describedby': shown ? tooltipId : undefined, + }); + + let tooltip: React.ReactPortal | null = null; + if (shown) { + const floatingStyle = convertFloatingDataToReactCSSProperties( + positionStrategy, + floatingDataX, + floatingDataY, + ); + + if (styleProp) { + Object.assign(floatingStyle, styleProp); + } + + tooltip = createPortal( + <> + +
+ , + tooltipContainer, + ); + } + + useIsomorphicLayoutEffect( + function initialize() { + const referenceEl = childRef.current; + if (referenceEl) { + setTooltipContainer( + referenceEl.closest(`[${onboardingTooltipContainerAttr}]`), // eslint-disable-line no-restricted-properties + ); + setPositionStrategy(referenceEl.style.position === 'fixed' ? 'fixed' : 'absolute'); + refs.setReference(referenceEl); + } + }, + [childRef], + ); + + if (process.env.NODE_ENV === 'development') { + const multiChildren = React.Children.count(children) > 1; + // Empty children is a noop + const primitiveChild = hasReactNode(children) && typeof children !== 'object'; + (multiChildren || primitiveChild) && + warn( + [ + 'children должен быть одним React элементом, получено', + multiChildren && 'несколько', + primitiveChild && JSON.stringify(children), + ] + .filter(Boolean) + .join(' '), + 'error', + ); + + if (refs.reference.current && !tooltipContainer) { + throw new Error('Use TooltipContainer for Tooltip outside Panel (see docs)'); + } + } + + return ( + + {child} + {tooltip} + + ); +}; + +function getArrowOffsetMiddleware( + cornerOffset: number, + cornerAbsoluteOffset?: number, +): UseFloatingMiddleware { + return { + name: 'arrowOffset', + fn({ placement, middlewareData }) { + if (!middlewareData.arrow) { + return Promise.resolve({}); + } + + const isVerticalPlacement = (placement: PlacementWithAuto) => + placement.startsWith('top') || placement.startsWith('bottom'); + + if (isVerticalPlacement(placement)) { + if (cornerAbsoluteOffset !== undefined) { + middlewareData.arrow.x = cornerAbsoluteOffset; + } else if (middlewareData.arrow.x !== undefined) { + middlewareData.arrow.x += cornerOffset; + } + } else { + if (cornerAbsoluteOffset !== undefined) { + middlewareData.arrow.y = cornerAbsoluteOffset; + } else if (middlewareData.arrow.y !== undefined) { + middlewareData.arrow.y += cornerOffset; + } + } + return Promise.resolve({}); + }, + }; +} diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipContainer.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipContainer.tsx new file mode 100644 index 0000000000..9c531100cf --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipContainer.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import type { HasDataAttribute } from '../../types'; + +export const onboardingTooltipContainerAttr = 'data-onboarding-tooltip-container'; + +type OnboardingTooltipContainerProps = React.HTMLAttributes & + HasDataAttribute & { + fixed?: boolean; + }; + +export const OnboardingTooltipContainer = React.forwardRef< + HTMLDivElement, + OnboardingTooltipContainerProps +>(({ fixed = false, ...props }, ref) => { + props[onboardingTooltipContainerAttr] = fixed ? 'fixed' : 'true'; + return
; +}); + +OnboardingTooltipContainer.displayName = 'OnboardingTooltipContainer'; diff --git a/packages/vkui/src/components/OnboardingTooltip/Readme.md b/packages/vkui/src/components/OnboardingTooltip/Readme.md new file mode 100644 index 0000000000..9b8ea18e1b --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/Readme.md @@ -0,0 +1,318 @@ +`OnboardingTooltip` – это компонент для отрисовки подсказки, как правило в случаях, когда +пользователю хочется представить новый функционал. Это достаточно сложный с точки зрения управления +компонент, поэтому он требует подробной документации. + +Для показа тултипа по ховеру, воспользуйтесь [`Tooltip`](https://vkcom.github.io/VKUI/#/Tooltip). + +### Концепция + +Этот тултип служит для ознакомительных целей. То есть показывать его надо один раз, запоминая факт показа между +сессиями. Рекомендуется показывать тултип сразу после того, как нуждающийся в нем элемент появился в зоне видимости. +Это значит, что если, например, до элемента нужно скроллить, то показывать тултип нужно не сразу при попадании +на страницу, а именно в момент, когда пользователь доскроллил до элемента. + +> **Важно** +> +> На странице не может быть два одновременно показанных тултипа. Они всегда должны показываться +> последовательно: следующий показывается при закрытии текущего и так до конца. + +### API + +Если хочется снабдить какой-то элемент интерфейса подсказкой, достаточно просто «обернуть» его тултипом: + +```jsx static +import { OnboardingTooltip, Button } from '@vkontakte/vkui'; + + + +; +``` + +О возможностях тултипа можно прочитать в описании свойств и методов. + +```jsx +const Example = () => { + const [tooltip, setTooltip] = React.useState(true); + const [tooltip2, setTooltip2] = React.useState(true); + const [tooltip3, setTooltip3] = React.useState(false); + const [activePanel, setActivePanel] = React.useState('tooltip'); + + return ( + + + OnboardingTooltip + + + Музыка + Видео + Игры + Закладки + Документы + Денежные переводы + + + + setTooltip(false)} + offsetByMainAxis={8} + offsetByCrossAxis={10} + > + setActivePanel('tooltip2')}>VK Pay + + + + + + { + setTooltip2(false); + setTooltip3(true); + }} + text="Нажмите на кнопку, если хотите вернуться" + header="Назад" + > + setActivePanel('tooltip')} /> + + } + > + OnboardingTooltip + + + + setTooltip3(false)} + arrowCornerOffset={-6} + > + + + } + subtitle="Веб-сайт" + > + Команда ВКонтакте + + } subtitle="Музыкант"> + Robbie Williams + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + } subtitle="Издательский дом"> + ПостНаука + + + + + + ); +}; + +; +``` + +## OnboardingTooltipContainer + +Чтобы использовать тултип без [`Panel`](#/Panel), [`PanelHeader`](#/PanelHeader) или [`FixedLayout`](#/FixedLayout): + +- в скроллящемся контейнере — замените какой-нибудь элемент, внутри которого нет скролла, на `` и добавьте ему `position: relative` (или другую не-static). +- внутри `position: fixed` — `` + +```jsx { "props": { "layout": false } } +<> + + +
+ +
+
+ +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ + +
+ +
+
+
+ +``` + +## Цветовые варианты + +```jsx { "props": { "layout": false } } + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+``` + +## Кастомная стрелка – `ArrowIcon` + +> ⚠️ Для начала, следует ознакомиться с описанием параметра `ArrowIcon`. + +```jsx { "props": { "layout": false, "adaptivity": true } } +const ARROW_HEIGHT = 11; + +/** + * @param {React.SVGAttributes} props + */ +const CustomIcon = (props) => { + return ( + + + + ); +}; + +const App = () => { + return ( + + +
+ Якорь +
+
+
+ ); +}; + +; +``` diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png new file mode 100644 index 0000000000..7f468e2781 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed6e7d694b5475b1075211af333e496185a3aa4131fe696ca52ee5a440c8318b +size 53977 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png new file mode 100644 index 0000000000..ad1a8fb45c --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:875d8c9cb21fdd373a5b263002929b112bb033a4cf99e0d75a4b49d71d7e438d +size 60931 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png new file mode 100644 index 0000000000..9df8407c10 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee33df886a72fc1a6d1e10249eb162a38ac14f2991b2d772b3b938f2701b4e9e +size 50316 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png new file mode 100644 index 0000000000..85c91cbe48 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc320751bbfd8c49b642f87dd6f627b037c51a61fc980d667a021fdb1f7294f3 +size 57079 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png new file mode 100644 index 0000000000..7084cbdd89 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a224376fc4e5507ea9cc2b79231ca3c2be1115a9478e6ab8fb2c026a5c074ccc +size 54282 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png new file mode 100644 index 0000000000..e8a370bd4e --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6d048deb1ff6427dab6a56ce3d759a3afa98bb72147bda4f5d336a97cb6a946 +size 61130 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png new file mode 100644 index 0000000000..ef2b50d231 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f6c7e0e11e7993afb21f93eb470debbacc7656949e20fa39d7df29fd95afefb +size 69529 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png new file mode 100644 index 0000000000..181d9c11ff --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1515df4b88f5afa1a11910cc7696be3ecff9589ee5a134423e74ba4c66751b +size 81784 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png new file mode 100644 index 0000000000..182fe665d9 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d22fa9d03637eddd77dd4dd254d2929c336a1dcc4fbd03d627a5a7a938baec7 +size 49594 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png new file mode 100644 index 0000000000..03c0e1e1b1 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21dbb5b5892d7a4e529a01a440eeac1b17be11d7c323687d38b18e651f3d526b +size 55770 diff --git a/packages/vkui/src/components/Panel/Panel.tsx b/packages/vkui/src/components/Panel/Panel.tsx index c9f9f8262b..3346f3fa6a 100644 --- a/packages/vkui/src/components/Panel/Panel.tsx +++ b/packages/vkui/src/components/Panel/Panel.tsx @@ -5,8 +5,8 @@ import { NavIdProps } from '../../lib/getNavId'; import { HTMLAttributesWithRootRef } from '../../types'; import { AppRootContext } from '../AppRoot/AppRootContext'; import { NavPanelIdContext } from '../NavIdContext/NavIdContext'; +import { OnboardingTooltipContainer } from '../OnboardingTooltip/OnboardingTooltipContainer'; import { RootComponent } from '../RootComponent/RootComponent'; -import { TooltipContainer } from '../Tooltip/TooltipContainer'; import { Touch } from '../Touch/Touch'; import styles from './Panel.module.css'; @@ -39,7 +39,7 @@ export const Panel = ({ centered = false, children, nav, ...restProps }: PanelPr )} >
diff --git a/packages/vkui/src/components/PanelHeader/PanelHeader.tsx b/packages/vkui/src/components/PanelHeader/PanelHeader.tsx index 0116bcc754..fa9eb1a396 100644 --- a/packages/vkui/src/components/PanelHeader/PanelHeader.tsx +++ b/packages/vkui/src/components/PanelHeader/PanelHeader.tsx @@ -7,10 +7,10 @@ import { HasComponent, HasDataAttribute, HasRef, HTMLAttributesWithRootRef } fro import { useConfigProvider } from '../ConfigProvider/ConfigProviderContext'; import { FixedLayout } from '../FixedLayout/FixedLayout'; import { ModalRootContext } from '../ModalRoot/ModalRootContext'; +import { OnboardingTooltipContainer } from '../OnboardingTooltip/OnboardingTooltipContainer'; import { RootComponent } from '../RootComponent/RootComponent'; import { Separator } from '../Separator/Separator'; import { Spacing } from '../Spacing/Spacing'; -import { TooltipContainer } from '../Tooltip/TooltipContainer'; import { Text } from '../Typography/Text/Text'; import styles from './PanelHeader.module.css'; @@ -90,7 +90,7 @@ const PanelHeaderIn = ({ return ( - +
@@ -101,7 +101,7 @@ const PanelHeaderIn = ({ className={classNames(styles['PanelHeader__after'], 'vkuiInternalPanelHeader__after')} {...afterSlotProps} /> - + {separator && platform === 'vkcom' && ( )} diff --git a/packages/vkui/src/components/Popover/Popover.tsx b/packages/vkui/src/components/Popover/Popover.tsx index 0ab4973961..d3738863cf 100644 --- a/packages/vkui/src/components/Popover/Popover.tsx +++ b/packages/vkui/src/components/Popover/Popover.tsx @@ -2,55 +2,62 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { usePatchChildren } from '../../hooks/usePatchChildren'; import { injectAriaExpandedPropByRole } from '../../lib/accessibility'; -import { createPortal } from '../../lib/createPortal'; import { animationFadeClassNames, transformOriginClassNames } from '../../lib/cssAnimation'; -import { getDocumentBody } from '../../lib/dom'; import { + type FloatingComponentProps, + type FloatingContentRenderProp, type OnShownChange, + useFloatingMiddlewaresBootstrap, useFloatingWithInteractions, - type UseFloatingWithInteractionsProps, - type UseFloatingWithInteractionsReturn, } from '../../lib/floating'; import type { HTMLAttributesWithRootRef } from '../../types'; +import { AppRootPortal } from '../AppRoot/AppRootPortal'; import { FocusTrap } from '../FocusTrap/FocusTrap'; import styles from './Popover.module.css'; +/** + * @alias + * @public + */ export type PopoverOnShownChange = OnShownChange; -export type PopoverContentRenderProp = ( - props: Pick, -) => React.ReactNode; +/** + * @alias + * @public + */ +export type PopoverContentRenderProp = FloatingContentRenderProp; +type AllowedFloatingComponentProps = Pick< + FloatingComponentProps, + | 'placement' + | 'trigger' + | 'content' + | 'hoverDelay' + | 'closeAfterClick' + | 'offsetByMainAxis' + | 'offsetByCrossAxis' + | 'defaultShown' + | 'shown' + | 'onShownChange' + | 'usePortal' + | 'sameWidth' + | 'hideWhenReferenceHidden' + | 'disabled' + | 'disableInteractive' + | 'disableCloseOnClickOutside' + | 'disableCloseOnEscKey' + | 'autoFocus' + | 'restoreFocus' + | 'children' + | 'zIndex' +>; + +/** + * @public + */ export interface PopoverProps - extends UseFloatingWithInteractionsProps, - Omit< - HTMLAttributesWithRootRef, - keyof UseFloatingWithInteractionsProps | 'content' - > { - /** - * Содержимое всплывающего окна. - * - * При передаче контента в виде [render prop](https://react.dev/reference/react/cloneElement#passing-data-with-a-render-prop), - * в аргументе функции можно получить метод `onClose`, с помощью которого можно программно закрывать - * всплывающее окно. - */ - content?: React.ReactNode | PopoverContentRenderProp; - /** - * Целевой элемент. Всплывающее окно появится возле него. - * - * > ⚠️ Если это пользовательский компонент, то он должен: - * > 1. предоставлять параметры либо `getRootRef`, либо `ref` (cм. `React.forwardRef()`) для получения ссылки на DOM-узел; - * > 2. принимать DOM атрибуты и события. - */ - children?: React.ReactElement; - /** - * Нужно ли при навигации с клавиатуры авто-фокусироваться на всплывающий элемент. - */ - autoFocus?: boolean; - /** - * Нужно ли после закрытия всплывающего элемента возвращать фокус на предыдущий активный элемент. - */ - restoreFocus?: boolean; + extends AllowedFloatingComponentProps, + Omit, keyof FloatingComponentProps> { /** * Отключает у всплывающего элемента стилизацию по умолчанию, а именно: * - background @@ -60,14 +67,6 @@ export interface PopoverProps * Используется в случае, если необходимо стилизовать по своему. */ noStyling?: boolean; - /** - * Перебивает zIndex заданный по умолчанию. - */ - zIndex?: number | string; - /** - * По умолчанию используется document.body. - */ - usePortal?: boolean | Element | DocumentFragment; } /** @@ -79,8 +78,11 @@ export const Popover = ({ trigger = 'click', content, hoverDelay = 150, + closeAfterClick, offsetByMainAxis = 8, offsetByCrossAxis = 0, + sameWidth, + hideWhenReferenceHidden, disabled, disableInteractive, disableCloseOnClickOutside, @@ -91,7 +93,7 @@ export const Popover = ({ shown: shownProp, onShownChange, - // Для createPortal + // Для AppRootPortal usePortal = true, // FocusTrapProps @@ -105,6 +107,13 @@ export const Popover = ({ role, ...restPopoverProps }: PopoverProps) => { + const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ + placement: expectedPlacement, + offsetByMainAxis, + offsetByCrossAxis, + sameWidth, + hideWhenReferenceHidden, + }); const { placement, shown, @@ -116,11 +125,11 @@ export const Popover = ({ onRestoreFocus, onEscapeKeyDown, } = useFloatingWithInteractions({ - placement: expectedPlacement, + middlewares, + placement: strictPlacement, trigger, hoverDelay, - offsetByMainAxis, - offsetByCrossAxis, + closeAfterClick, disabled, disableInteractive, disableCloseOnClickOutside, @@ -130,7 +139,7 @@ export const Popover = ({ onShownChange, }); - const [childRef, child] = usePatchChildren( + const [, child] = usePatchChildren( children, injectAriaExpandedPropByRole(referenceProps, shown, role), refs.setReference, @@ -140,36 +149,33 @@ export const Popover = ({ if (shown) { floatingProps.style.zIndex = String(zIndex); popover = ( -
- - {typeof content === 'function' ? content({ onClose }) : content} - -
+ +
+ + {typeof content === 'function' ? content({ onClose }) : content} + +
+
); } return ( {child} - {usePortal && popover - ? createPortal( - popover, - typeof usePortal !== 'boolean' ? usePortal : getDocumentBody(childRef.current), - ) - : popover} + {popover} ); }; diff --git a/packages/vkui/src/components/Popper/Popper.stories.tsx b/packages/vkui/src/components/Popper/Popper.stories.tsx index 90d3ba8ff4..93c8120dd6 100644 --- a/packages/vkui/src/components/Popper/Popper.stories.tsx +++ b/packages/vkui/src/components/Popper/Popper.stories.tsx @@ -26,7 +26,7 @@ export const Playground: Story = { {shown ? 'Закрыть' : 'Открыть'} {shown && ( - +
Привет
)} diff --git a/packages/vkui/src/components/Popper/Popper.tsx b/packages/vkui/src/components/Popper/Popper.tsx index 1bf0a801d9..5d4ba51457 100644 --- a/packages/vkui/src/components/Popper/Popper.tsx +++ b/packages/vkui/src/components/Popper/Popper.tsx @@ -4,10 +4,10 @@ import { usePrevious } from '../../hooks/usePrevious'; import { autoUpdateFloatingElement, convertFloatingDataToReactCSSProperties, + type FloatingComponentProps, type Placement, useFloating, useFloatingMiddlewaresBootstrap, - type UseFloatingMiddlewaresBootstrapOptions, type VirtualElement, } from '../../lib/floating'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; @@ -25,30 +25,34 @@ import { import { RootComponent } from '../RootComponent/RootComponent'; import styles from './Popper.module.css'; -export interface PopperRenderContentProps { - className: string; -} - export type PopperArrowProps = Omit< PopperArrowPropsPrivate, 'getRootRef' | 'coords' | 'placement' | 'Icon' >; +type AllowedFloatingComponentProps = Pick< + FloatingComponentProps, + | 'arrow' + | 'arrowRef' + | 'arrowHeight' + | 'arrowPadding' + | 'hoverDelay' + | 'placement' + | 'offsetByMainAxis' + | 'offsetByCrossAxis' + | 'shown' + | 'onShownChange' + | 'defaultShown' + | 'hideWhenReferenceHidden' + | 'sameWidth' + | 'zIndex' + | 'usePortal' + | 'customMiddlewares' +>; + export interface PopperCommonProps - extends HTMLAttributesWithRootRef, - Pick< - UseFloatingMiddlewaresBootstrapOptions, - | 'placement' - | 'sameWidth' - | 'hideWhenReferenceHidden' - | 'offsetByMainAxis' - | 'offsetByCrossAxis' - | 'arrow' - | 'arrowRef' - | 'arrowPadding' - | 'arrowHeight' - | 'customMiddlewares' - > { + extends AllowedFloatingComponentProps, + Omit, keyof AllowedFloatingComponentProps> { /** * Позволяет набросить на стрелку пользовательские атрибуты. */ @@ -67,16 +71,6 @@ export interface PopperCommonProps * 5. Убедитесь, что SVG и её элементы наследует цвет через `fill="currentColor"`. */ ArrowIcon?: PopperArrowPropsPrivate['Icon']; - /** - * Принудительно использовать портал. - */ - forcePortal?: boolean; - /** - * Кастомный root-элемент портала. - * При передаче вместе с `forcePorta=true` игнорируется `portalRoot` и `disablePortal` - * из контекста `AppRoot`. - */ - portalRoot?: HTMLElement | React.RefObject | null; /** * Подписывается на изменение геометрии `targetRef`, чтобы пересчитать свою позицию. */ @@ -118,8 +112,7 @@ export const Popper = ({ targetRef, getRootRef, children, - portalRoot, - forcePortal = true, + usePortal = true, style: styleProp, onPlacementChange, ...restProps @@ -203,9 +196,5 @@ export const Popper = ({ ); - return ( - - {dropdown} - - ); + return {dropdown}; }; diff --git a/packages/vkui/src/components/PopperArrow/PopperArrow.tsx b/packages/vkui/src/components/PopperArrow/PopperArrow.tsx index c33131ca89..210a19b492 100644 --- a/packages/vkui/src/components/PopperArrow/PopperArrow.tsx +++ b/packages/vkui/src/components/PopperArrow/PopperArrow.tsx @@ -20,17 +20,20 @@ export interface PopperArrowProps extends HTMLAttributesWithRootRef, HasDataAttribute { coords?: Coords; - placement: Placement; + placement?: Placement; iconStyle?: React.CSSProperties; iconClassName?: string; Icon?: React.ComponentType>; } +/** + * TODO [>=6.1.0] Переименовать в FloatingArrow + */ export const PopperArrow = ({ coords, iconStyle, iconClassName, - placement, + placement = 'bottom', getRootRef, Icon = DefaultIcon, ...restProps diff --git a/packages/vkui/src/components/Slider/SliderThumb/SliderThumb.tsx b/packages/vkui/src/components/Slider/SliderThumb/SliderThumb.tsx index 74e9b327a2..df007145fe 100644 --- a/packages/vkui/src/components/Slider/SliderThumb/SliderThumb.tsx +++ b/packages/vkui/src/components/Slider/SliderThumb/SliderThumb.tsx @@ -108,14 +108,16 @@ export const SliderThumb = ({ )} diff --git a/packages/vkui/src/components/TextTooltip/Readme.md b/packages/vkui/src/components/TextTooltip/Readme.md deleted file mode 100644 index d3a1a659b2..0000000000 --- a/packages/vkui/src/components/TextTooltip/Readme.md +++ /dev/null @@ -1,11 +0,0 @@ -> **Важно** -> -> Это нестабильный компонент. Его API может меняться в рамках одной мажорной версии. [Подробнее про нестабильные компоненты](https://vkcom.github.io/VKUI/#/Unstable). - -Тултип, открывающийся при наведении мыши на `children`. В качестве содержимого тултипа рекомендуется использовать только текст. - -```jsx { "props": { "layout": false, "iframe": false } } - - - -``` diff --git a/packages/vkui/src/components/TextTooltip/TextTooltip.module.css b/packages/vkui/src/components/TextTooltip/TextTooltip.module.css deleted file mode 100644 index 9bae3abd82..0000000000 --- a/packages/vkui/src/components/TextTooltip/TextTooltip.module.css +++ /dev/null @@ -1,59 +0,0 @@ -.TextTooltip { - border-radius: var(--vkui--size_border_radius--regular); - background-color: var(--vkui--color_background_modal); - padding-block: 8px 9px; - padding-inline: 12px; - color: var(--vkui--color_text_primary); - box-shadow: var(--vkui--elevation3); - animation: texttooltip-fade-in 0.2s ease; -} - -.TextTooltip__arrow { - color: var(--vkui--color_background_modal); -} - -.TextTooltip--appearance-accent { - background-color: var(--vkui--color_background_accent_tint); - color: var(--vkui--color_text_contrast); -} - -.TextTooltip--appearance-accent .TextTooltip__arrow { - color: var(--vkui--color_background_accent_tint); -} - -.TextTooltip--appearance-white { - background-color: var(--vkui--color_background_contrast); - color: var(--vkui--color_text_primary_invariably); -} - -.TextTooltip--appearance-white .TextTooltip__arrow { - color: var(--vkui--color_background_contrast); -} - -.TextTooltip--appearance-black { - background-color: var(--vkui--color_background_contrast_inverse); - color: var(--vkui--color_text_contrast); -} - -.TextTooltip--appearance-black .TextTooltip__arrow { - color: var(--vkui--color_background_contrast_inverse); -} - -.TextTooltip--appearance-inversion { - background-color: var(--vkui--color_background_modal_inverse); - color: var(--vkui--color_text_contrast_themed); -} - -.TextTooltip--appearance-inversion .TextTooltip__arrow { - color: var(--vkui--color_background_modal_inverse); -} - -@keyframes texttooltip-fade-in { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} diff --git a/packages/vkui/src/components/TextTooltip/TextTooltip.stories.tsx b/packages/vkui/src/components/TextTooltip/TextTooltip.stories.tsx deleted file mode 100644 index 58f8645e35..0000000000 --- a/packages/vkui/src/components/TextTooltip/TextTooltip.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import { DisableCartesianParam } from '../../storybook/constants'; -import { Button } from '../Button/Button'; -import { TextTooltip, TextTooltipProps } from './TextTooltip'; - -const story: Meta = { - title: 'Poppers/TextTooltip', - component: TextTooltip, - parameters: DisableCartesianParam, -}; - -export default story; - -type Story = StoryObj; - -export const Playground: Story = { - render: (args) => ( - - - - ), - args: { - text: 'Привет', - }, -}; diff --git a/packages/vkui/src/components/TextTooltip/TextTooltip.tsx b/packages/vkui/src/components/TextTooltip/TextTooltip.tsx deleted file mode 100644 index a8d47b1e81..0000000000 --- a/packages/vkui/src/components/TextTooltip/TextTooltip.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from 'react'; -import { classNames, hasReactNode } from '@vkontakte/vkjs'; -import { HoverPopper, HoverPopperProps } from '../HoverPopper/HoverPopper'; -import { Subhead } from '../Typography/Subhead/Subhead'; -import styles from './TextTooltip.module.css'; - -const stylesAppearance = { - accent: styles['TextTooltip--appearance-accent'], - white: styles['TextTooltip--appearance-white'], - black: styles['TextTooltip--appearance-black'], - inversion: styles['TextTooltip--appearance-inversion'], -}; - -export interface TextTooltipProps - extends Omit { - /** - * Текст тултипа - */ - text?: React.ReactNode; - /** - * Заголовок тултипа - */ - header?: React.ReactNode; - /** - * Стиль отображения подсказки - */ - appearance?: 'accent' | 'neutral' | 'white' | 'black' | 'inversion'; -} - -/** - * @see https://vkcom.github.io/VKUI/#/TextTooltip - */ -export const TextTooltip = ({ - children, - text, - header, - appearance = 'neutral', - className, - ...popperProps -}: TextTooltipProps) => { - return ( - - {hasReactNode(header) && {header}} - {hasReactNode(text) && {text}} - - } - {...popperProps} - > - {children} - - ); -}; diff --git a/packages/vkui/src/components/Tooltip/Readme.md b/packages/vkui/src/components/Tooltip/Readme.md index 5bd5ba10a7..a4034544cc 100644 --- a/packages/vkui/src/components/Tooltip/Readme.md +++ b/packages/vkui/src/components/Tooltip/Readme.md @@ -1,317 +1,7 @@ -`Tooltip` – это **экспериментальный** компонент для отрисовки подсказки, как правило в случаях, когда пользователю -хочется представить новый функционал. Это достаточно сложный с точки зрения управления компонент, поэтому он -требует подробной документации. +Тултип, открывающийся при наведении мыши на `children`. В качестве содержимого тултипа рекомендуется использовать только текст. -Для показа тултипа по ховеру, воспользуйтесь [`TextTooltip`](https://vkcom.github.io/VKUI/#/TextTooltip). - -### Концепция - -Этот тултип служит для ознакомительных целей. То есть показывать его надо один раз, запоминая факт показа между -сессиями. Рекомендуется показывать тултип сразу после того, как нуждающийся в нем элемент появился в зоне видимости. -Это значит, что если, например, до элемента нужно скроллить, то показывать тултип нужно не сразу при попадании -на страницу, а именно в момент, когда пользователь доскроллил до элемента. - -> **Важно** -> -> На странице не может быть два одновременно показанных тултипа. Они всегда должны показываться -> последовательно: следующий показывается при закрытии текущего и так до конца. - -### API - -Если хочется снабдить какой-то элемент интерфейса подсказкой, достаточно просто «обернуть» его тултипом: - -```jsx static -import { Tooltip, Button } from '@vkontakte/vkui'; - - - -; -``` - -О возможностях тултипа можно прочитать в описании свойств и методов. - -```jsx -const Example = () => { - const [tooltip, setTooltip] = React.useState(true); - const [tooltip2, setTooltip2] = React.useState(true); - const [tooltip3, setTooltip3] = React.useState(false); - const [activePanel, setActivePanel] = React.useState('tooltip'); - - return ( - - - Tooltip - - - Музыка - Видео - Игры - Закладки - Документы - Денежные переводы - - - - setTooltip(false)} - offsetX={10} - > - setActivePanel('tooltip2')}>VK Pay - - - - - - { - setTooltip2(false); - setTooltip3(true); - }} - text="Нажмите на кнопку, если хотите вернуться" - header="Назад" - > - setActivePanel('tooltip')} /> - - } - > - Tooltip - - - - setTooltip3(false)} - cornerOffset={-6} - > - - - } - subtitle="Веб-сайт" - > - Команда ВКонтакте - - } subtitle="Музыкант"> - Robbie Williams - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - - - - - ); -}; - -; -``` - -## TooltipContainer - -Чтобы использовать тултип без `Panel` / `PanelHeader` / `FixedLayout`: - -- в скроллящемся контейнере — замените какой-нибудь элемент, внутри которого нет скролла, на `` и добавьте ему `position: relative` (или другую не-static). -- внутри `position: fixed` — `` - -```jsx { "props": { "layout": false } } -<> - - -
- -
-
- -
- -
-
-
- - -
- -
-
-
- - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
-
-
- - -
- -
-
-
- -``` - -## Цветовые варианты - -```jsx { "props": { "layout": false } } - - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
-
-``` - -## Кастомная стрелка – `ArrowIcon` - -> ⚠️ Для начала, следует ознакомиться с описанием параметра `ArrowIcon`. - -```jsx { "props": { "layout": false, "adaptivity": true } } -const ARROW_HEIGHT = 11; - -/** - * @param {React.SVGAttributes} props - */ -const CustomIcon = (props) => { - return ( - - - - ); -}; - -const App = () => { - return ( - - -
- Якорь -
-
-
- ); -}; - -; +```jsx { "props": { "layout": false, "iframe": false } } + + + ``` diff --git a/packages/vkui/src/components/Tooltip/Tooltip.e2e.tsx b/packages/vkui/src/components/Tooltip/Tooltip.e2e.tsx deleted file mode 100644 index fa947fcece..0000000000 --- a/packages/vkui/src/components/Tooltip/Tooltip.e2e.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import * as React from 'react'; -import { test } from '@vkui-e2e/test'; -import { TooltipPlayground } from './Tooltip.e2e-playground'; - -test('Tooltip', async ({ mount, expectScreenshotClippedToContent, componentPlaygroundProps }) => { - await mount(); - await expectScreenshotClippedToContent(); -}); diff --git a/packages/vkui/src/components/Tooltip/Tooltip.stories.tsx b/packages/vkui/src/components/Tooltip/Tooltip.stories.tsx index 494421792d..ad26e6469a 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.stories.tsx +++ b/packages/vkui/src/components/Tooltip/Tooltip.stories.tsx @@ -1,17 +1,8 @@ import * as React from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { withVKUILayout } from '../../storybook/VKUIDecorators'; -import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants'; -import { Avatar } from '../Avatar/Avatar'; -import { Group } from '../Group/Group'; -import { List } from '../List/List'; -import { Panel } from '../Panel/Panel'; -import { PanelHeader } from '../PanelHeader/PanelHeader'; -import { PanelHeaderBack } from '../PanelHeaderBack/PanelHeaderBack'; -import { SimpleCell } from '../SimpleCell/SimpleCell'; -import { View } from '../View/View'; +import { DisableCartesianParam } from '../../storybook/constants'; +import { Button } from '../Button/Button'; import { Tooltip, TooltipProps } from './Tooltip'; -import { TooltipContainer } from './TooltipContainer'; const story: Meta = { title: 'Poppers/Tooltip', @@ -25,262 +16,11 @@ type Story = StoryObj; export const Playground: Story = { render: (args) => ( - -
- - - -
-
+ + + ), args: { - text: 'Tooltip', - }, -}; - -export const ShowCase: Story = { - render: function Render() { - const [tooltip, setTooltip] = React.useState(true); - const [tooltip2, setTooltip2] = React.useState(true); - const [tooltip3, setTooltip3] = React.useState(false); - const [activePanel, setActivePanel] = React.useState('tooltip'); - - return ( - - - Tooltip - - - Музыка - Видео - Игры - Закладки - Документы - Денежные переводы - - - - setTooltip(false)} - offsetX={10} - > - setActivePanel('tooltip2')}>VK Pay - - - - - - { - setTooltip2(false); - setTooltip3(true); - }} - text="Нажмите на кнопку, если хотите вернуться" - header="Назад" - > - setActivePanel('tooltip')} /> - - } - > - Tooltip - - - - setTooltip3(false)} - cornerOffset={-6} - > - - - } - subtitle="Веб-сайт" - > - Команда ВКонтакте - - } subtitle="Музыкант"> - Robbie Williams - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - } subtitle="Издательский дом"> - ПостНаука - - - - - - ); - }, - decorators: [withVKUILayout], - parameters: CanvasFullLayout, -}; - -export const WithTooltipContainer: Story = { - render: () => ( - <> - - -
- -
-
- -
- -
-
-
- - -
- -
-
-
- - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
-
-
- - -
- -
-
-
- - ), -}; - -export const CustomArrowIcon: Story = { - render: () => { - const ARROW_HEIGHT = 11; - - const CustomIcon = (props: React.SVGAttributes) => { - return ( - - - - ); - }; - - return ( - - -
- Якорь -
-
-
- ); + text: 'Привет', }, }; diff --git a/packages/vkui/src/components/Tooltip/Tooltip.test.tsx b/packages/vkui/src/components/Tooltip/Tooltip.test.tsx index 82acdf2f46..2669d4254d 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.test.tsx +++ b/packages/vkui/src/components/Tooltip/Tooltip.test.tsx @@ -1,102 +1,11 @@ import * as React from 'react'; -import { Fragment, HtmlHTMLAttributes, ReactElement } from 'react'; -import { render, screen } from '@testing-library/react'; -import { baselineComponent, waitForFloatingPosition } from '../../testing/utils'; -import { HasRootRef } from '../../types'; +import { baselineComponent } from '../../testing/utils'; import { Tooltip } from './Tooltip'; -import { TooltipContainer } from './TooltipContainer'; -const renderTooltip = async (jsx: ReactElement) => { - render({jsx}); - await waitForFloatingPosition(); -}; -const RootRef = ({ - getRootRef, - ...props -}: HasRootRef & HtmlHTMLAttributes) => ( -
-); - -describe('Tooltip', () => { - baselineComponent( - (props) => ( - - -
- - - ), - { forward: false, getRootRef: false }, - ); - - it('renders tooltip when isShown=true', async () => { - await renderTooltip( - -
- , - ); - expect(screen.getByText('text')).toBeTruthy(); - }); - - it('supports child with getRootRef', async () => { - await renderTooltip( - - - , - ); - expect(screen.getByText('text')).toBeTruthy(); - }); - - it('does not create extra markup when isShown=false', () => { - render( - - -
- - , - ); - expect(screen.queryByText('text')).toBeFalsy(); - const container = screen.getByTestId('container'); - expect(container.childElementCount).toBe(1); - expect(container.firstElementChild).toBe(screen.getByTestId('xxx')); - }); - - it('does not explode when children does not accept ref', () => { - expect(() => render()).not.toThrow(); - expect(() => render({'text' as any})).not.toThrow(); - expect(() => - render({[
,
] as any}), - ).not.toThrow(); - expect(() => - render( - - - text -
- - , - ), - ).not.toThrow(); - }); - - describe('preserves child ref', () => { - it('on DOM child', async () => { - const ref = jest.fn(); - await renderTooltip( - -
- , - ); - expect(ref).toHaveBeenCalledWith(screen.getByTestId('xxx')); - }); - it('on VKUI child', async () => { - const ref = jest.fn(); - await renderTooltip( - - - , - ); - expect(ref).toHaveBeenCalledWith(screen.getByTestId('xxx')); - }); - }); +describe(Tooltip, () => { + baselineComponent((props) => ( + +
Target
+
+ )); }); diff --git a/packages/vkui/src/components/Tooltip/Tooltip.tsx b/packages/vkui/src/components/Tooltip/Tooltip.tsx index 4e06d19559..d8332651d7 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.tsx +++ b/packages/vkui/src/components/Tooltip/Tooltip.tsx @@ -1,302 +1,203 @@ import * as React from 'react'; -import ReactDOM from 'react-dom'; -import { hasReactNode } from '@vkontakte/vkjs'; +import { classNames, hasReactNode } from '@vkontakte/vkjs'; +import { getWindow } from '@vkontakte/vkui-floating-ui/utils/dom'; import { useExternRef } from '../../hooks/useExternRef'; +import { usePatchChildren } from '../../hooks/usePatchChildren'; +import { Keys, pressedKey } from '../../lib/accessibility'; +import { animationFadeClassNames } from '../../lib/cssAnimation'; import { - arrowMiddleware, - autoPlacementMiddleware, - autoUpdateFloatingElement, - checkIsNotAutoPlacement, - convertFloatingDataToReactCSSProperties, - flipMiddleware, - getAutoPlacementAlign, - offsetMiddleware, - type Placement, - type PlacementWithAuto, - shiftMiddleware, - useFloating, - type UseFloatingMiddleware, + type FloatingComponentProps, + getArrowCoordsByMiddlewareData, + useFloatingMiddlewaresBootstrap, + useFloatingWithInteractions, } from '../../lib/floating'; -import { warnOnce } from '../../lib/warnOnce'; -import { HasRootRef } from '../../types'; -import { useNavTransition } from '../NavTransitionContext/NavTransitionContext'; -import { TOOLTIP_MAX_WIDTH, TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase'; -import { tooltipContainerAttr } from './TooltipContainer'; -import styles from './Tooltip.module.css'; - -/** - * Перебиваем `ref`. - * - * В оригинальном `React.DOMElement` задаётся `React.LegacyRef`, в котором есть `string`. - * Когда как `{ ref: "string" }` уже давно депрекейтнут. - */ -interface DOMElement

| React.SVGAttributes, T extends Element> - extends React.DOMElement { - ref: React.Ref; -} - -const isDOMTypeElement = < - P extends React.HTMLAttributes | React.SVGAttributes, - T extends Element, ->( - element: React.ReactElement, -): element is DOMElement => { - return React.isValidElement(element) && typeof element.type === 'string'; -}; - -const warn = warnOnce('Tooltip'); - -export interface TooltipProps - extends Omit< - TooltipBaseProps, - 'arrowCoords' | 'arrowPlacement' | 'getArrowRef' | 'floatingStyle' | 'withArrow' - > { - /** - * **Важно**: если в `children` передан React-компонент, то необходимо убедиться в том, что он поддерживает - * свойство `getRootRef`, которое должно возвращаться ссылку на корневой DOM-элемент компонента, - * иначе тултип показан не будет. Если передан React-element, то такой проблемы нет. - */ - children: React.ReactElement> | React.ReactElement; - /** - * Если передан `false`, то рисуется просто `children`. - */ - isShown?: boolean; - alignX?: 'center' | 'left' | 'right'; - /** - * Положение по вертикали (расположение над или под `children`). - * Если не задано, позиция по вертикали определятся автоматически - */ - alignY?: 'top' | 'bottom'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; +import { AppRootPortal } from '../AppRoot/AppRootPortal'; +import { TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase'; +import { Subhead } from '../Typography/Subhead/Subhead'; + +type AllowedFloatingComponentProps = Pick< + FloatingComponentProps, + | 'arrowHeight' + | 'arrowPadding' + | 'hoverDelay' + | 'placement' + | 'offsetByMainAxis' + | 'offsetByCrossAxis' + | 'defaultShown' + | 'onShownChange' + | 'hideWhenReferenceHidden' + | 'children' + | 'zIndex' + | 'usePortal' +>; + +type AllowedTooltipBaseProps = Omit; + +export interface TooltipProps extends AllowedFloatingComponentProps, AllowedTooltipBaseProps { /** - * Сдвиг по горизонтали (относительно портала, в котором рисуется тултип). + * Если передан, то тултип перейдёт контролируемый компонент. + * Используйте совместно с `onShawnChange`. + * + * > Если нужно показать тултип лишь при первом рендере, то лучше используйте `defaultShown`. */ - offsetX?: number; + shown?: boolean; /** - * Сдвиг по вертикали (относительно портала, в котором рисуется тултип). + * Добавляет возможность наводить на тултип. */ - offsetY?: number; + enableInteractive?: boolean; /** - * Отображать ли стрелку, указывающую на якорный элемент + * Скрывает стрелку, указывающую на якорный элемент. */ - arrow?: boolean; + disableArrow?: boolean; /** - * Безопасная зона вокруг стрелки, чтобы та не выходила за края контента. + * Отключает закрытие по клику. */ - arrowPadding?: number; - /** - * Сдвиг стрелочки относительно центра дочернего элемента. - */ - cornerOffset?: number; - /** - * Сдвиг стрелочки относительно ширины тултипа - */ - cornerAbsoluteOffset?: number; - /** - * Callback, который вызывается при клике по любому месту в пределах экрана. - */ - onClose?: () => void; - /** - * По умолчанию компонент выберет наилучшее расположение сам. Но его можно задать извне с помощью этого свойства - */ - placement?: PlacementWithAuto; -} - -function mapAlignX(x: TooltipProps['alignX']) { - switch (x) { - case 'left': - return 'start'; - case 'right': - return 'end'; - default: - return ''; - } -} -function getDefaultPlacement( - alignX: TooltipProps['alignX'], - alignY: TooltipProps['alignY'], -): Placement { - return [alignY || 'bottom', mapAlignX(alignX || 'left')] - .filter((p) => !!p) - .join('-') as Placement; -} -function isVerticalPlacement(placement: PlacementWithAuto) { - return placement.startsWith('top') || placement.startsWith('bottom'); + disableCloseAfterClick?: boolean; } /** * @see https://vkcom.github.io/VKUI/#/Tooltip */ export const Tooltip = ({ + // UseFloatingMiddlewaresBootstrapOptions + placement: placementProp = 'bottom', + arrowPadding = 10, + arrowHeight = 8, + offsetByMainAxis = 8, + offsetByCrossAxis = 0, + hideWhenReferenceHidden, + + // useFloatingWithInteractions + defaultShown, + shown: shownProp, + onShownChange, + hoverDelay = 150, + + // инверсированные св-ва для useFloatingWithInteractions + enableInteractive = false, + disableArrow = false, + disableCloseAfterClick = false, + + // Reference children, - isShown: isShownProp = true, - offsetX = 0, - offsetY = 15, - alignX, - alignY, - onClose, - cornerOffset = 0, - cornerAbsoluteOffset, - arrow = true, - arrowPadding = 14, - placement: placementProp, - maxWidth = TOOLTIP_MAX_WIDTH, - ...restProps -}: TooltipProps) => { - const [arrowRef, setArrowRef] = React.useState(null); - const [target, setTarget] = React.useState(null); - /* eslint-disable no-restricted-properties */ - const tooltipContainer = React.useMemo( - () => target?.closest(`[${tooltipContainerAttr}]`), - [target], - ); - const { entering } = useNavTransition(); - const isShown = isShownProp && tooltipContainer && !entering; - - const placement = placementProp || getDefaultPlacement(alignX, alignY); - const isNotAutoPlacement = checkIsNotAutoPlacement(placement); - if (process.env.NODE_ENV === 'development') { - const multiChildren = React.Children.count(children) > 1; - // Empty children is a noop - const primitiveChild = hasReactNode(children) && typeof children !== 'object'; - (multiChildren || primitiveChild) && - warn( - [ - 'children должен быть одним React элементом, получено', - multiChildren && 'несколько', - primitiveChild && JSON.stringify(children), - ] - .filter(Boolean) - .join(' '), - 'error', - ); - } - - const floatingPositionStrategy = React.useMemo( - () => (target?.style.position === 'fixed' ? 'fixed' : 'absolute'), - [target], - ); - - if (process.env.NODE_ENV === 'development' && target && !tooltipContainer) { - throw new Error('Use TooltipContainer for Tooltip outside Panel (see docs)'); - } - - const memoizedMiddlewares = React.useMemo(() => { - const middlewares: UseFloatingMiddleware[] = [ - offsetMiddleware({ - crossAxis: offsetX, - mainAxis: offsetY, - }), - ]; + // AppRootProps + usePortal, + + // TooltipBaseProps + id: idProp, + getRootRef, + text, + header, + appearance = 'neutral', + style: styleProp, + className, + zIndex = 'var(--vkui--z_index_popout)', + ...popperProps +}: TooltipProps) => { + const generatedId = React.useId(); + const tooltipId = idProp || generatedId; - // см. https://floating-ui.com/docs/flip#conflict-with-autoplacement - if (isNotAutoPlacement) { - middlewares.push(flipMiddleware()); - } else { - middlewares.push( - autoPlacementMiddleware({ - alignment: placement ? getAutoPlacementAlign(placement) : null, - }), - ); - } + const [arrowRef, setArrowRef] = React.useState(null); + const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ + placement: placementProp, - middlewares.push(shiftMiddleware()); + offsetByMainAxis, + offsetByCrossAxis, - // см. https://floating-ui.com/docs/arrow#order - if (arrow) { - middlewares.push( - arrowMiddleware({ - element: arrowRef, - padding: arrowPadding, - }), - ); - middlewares.push({ - name: 'arrowOffset', - fn({ placement, middlewareData }) { - if (!middlewareData.arrow) { - return Promise.resolve({}); - } - if (isVerticalPlacement(placement)) { - if (cornerAbsoluteOffset !== undefined) { - middlewareData.arrow.x = cornerAbsoluteOffset; - } else if (middlewareData.arrow.x !== undefined) { - middlewareData.arrow.x += cornerOffset; - } - } else { - if (cornerAbsoluteOffset !== undefined) { - middlewareData.arrow.y = cornerAbsoluteOffset; - } else if (middlewareData.arrow.y !== undefined) { - middlewareData.arrow.y += cornerOffset; - } - } - return Promise.resolve({}); - }, - }); - } + hideWhenReferenceHidden, - return middlewares; - }, [ - arrow, + arrow: !disableArrow, arrowRef, arrowPadding, - cornerAbsoluteOffset, - cornerOffset, - offsetX, - offsetY, - placement, - isNotAutoPlacement, - ]); - + arrowHeight, + }); const { - x: floatingDataX, - y: floatingDataY, - placement: resolvedPlacement, + shown, + willBeHide, + placement, refs, - middlewareData: { arrow: arrowCoords }, - } = useFloating({ - strategy: floatingPositionStrategy, - placement: isNotAutoPlacement ? placement : undefined, - middleware: memoizedMiddlewares, - whileElementsMounted: autoUpdateFloatingElement, + referenceProps, + floatingProps, + middlewareData, + onEscapeKeyDown, + } = useFloatingWithInteractions({ + defaultShown, + shown: shownProp, + onShownChange, + placement: strictPlacement, + trigger: shownProp !== undefined ? 'manual' : ['hover', 'focus'], + hoverDelay, + closeAfterClick: !disableCloseAfterClick, + disableInteractive: !enableInteractive, + middlewares, }); - - const childRef = isDOMTypeElement, HTMLElement>(children) - ? children.ref - : React.isValidElement>(children) - ? children.props.getRootRef - : null; - const patchedRef = useExternRef(setTarget, refs.setReference, childRef); - const child = React.isValidElement(children) - ? React.cloneElement(children, { - [isDOMTypeElement(children) ? 'ref' : 'getRootRef']: patchedRef, - }) - : children; + const tooltipRef = useExternRef(getRootRef, refs.setFloating); + + let tooltip: React.ReactNode = null; + if (shown) { + referenceProps['aria-describedby'] = tooltipId; + floatingProps.style.zIndex = zIndex; + if (styleProp) { + Object.assign(floatingProps.style, styleProp); + } + tooltip = ( + + + {hasReactNode(header) && {header}} + {hasReactNode(text) && {text}} + + } + className={classNames( + willBeHide ? animationFadeClassNames.out : animationFadeClassNames.in, + className, + )} + /> + + ); + } + const [childRef, child] = usePatchChildren(children, referenceProps, refs.setReference); + + useIsomorphicLayoutEffect( + function handleGlobalKeyDownIfTooltipShown() { + if (!onEscapeKeyDown || !shown) { + return; + } + const handleKeyDown = (event: KeyboardEvent) => { + if (pressedKey(event) === Keys.ESCAPE) { + onEscapeKeyDown(); + } + }; + const doc = getWindow(childRef.current).document; + doc.addEventListener('keydown', handleKeyDown, { passive: true, capture: true }); + return () => { + doc.removeEventListener('keydown', handleKeyDown, { capture: true }); + }; + }, + [shown, childRef, onEscapeKeyDown], + ); return ( {child} - {isShown && - target != null && - ReactDOM.createPortal( - <> - -

- , - tooltipContainer, - )} + {tooltip} ); }; diff --git a/packages/vkui/src/components/Tooltip/TooltipContainer.tsx b/packages/vkui/src/components/Tooltip/TooltipContainer.tsx deleted file mode 100644 index 3e20e1a721..0000000000 --- a/packages/vkui/src/components/Tooltip/TooltipContainer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from 'react'; - -export const tooltipContainerAttr = 'data-tooltip-container'; -export const TooltipContainer = React.forwardRef< - HTMLDivElement, - React.HtmlHTMLAttributes & { fixed?: boolean } ->(function TooltipContainer({ fixed = false, ...props }, ref) { - (props as any)[tooltipContainerAttr] = fixed ? 'fixed' : 'true'; - return
; -}); diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-dark-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-dark-1-snap.png deleted file mode 100644 index a44af9f8c3..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-dark-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5668c177b1fe0282642821271b1d9867fa4c04965372f6cd5fdb27b8b11e917b -size 52830 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-light-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-light-1-snap.png deleted file mode 100644 index 933a58e5f0..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-android-chromium-light-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aff1201e1910646026d904fcb91f35cce21090d50320de9d2eac871fcaf001fc -size 59900 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-dark-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-dark-1-snap.png deleted file mode 100644 index ac2c20e36c..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-dark-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec77fb0ea977f0075465691dd73de3dd68bfec43ba2b6ebeffc48aef82db096f -size 49494 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-light-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-light-1-snap.png deleted file mode 100644 index d5a69af2e6..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-ios-webkit-light-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36e05fb3319d2a18558d0ac4b050cf37c8eb8b60ab8025f625d42a08237c045b -size 56174 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-dark-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-dark-1-snap.png deleted file mode 100644 index ad0fbe611b..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-dark-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0718afe07ef17e19fb5e37ed86ebba6516e014aa1193a58cdd2328493209475 -size 53605 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-light-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-light-1-snap.png deleted file mode 100644 index e933ab418e..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-chromium-light-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a56d1fa4c936a5fadca24678b4fb580d452189080414903ac190144ff099872c -size 60533 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-dark-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-dark-1-snap.png deleted file mode 100644 index 6088c2febc..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-dark-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34b134c7946e24a6b2c1c17fd6f12688b8b11dc1e6b3ab2dd984de44756d61db -size 70116 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-light-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-light-1-snap.png deleted file mode 100644 index 73ac0dd517..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-firefox-light-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8c8c32b796b24cbeb783367b8be07145e8f77ede641957eeee2983e878b5b79 -size 82360 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-dark-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-dark-1-snap.png deleted file mode 100644 index 4fff5665aa..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-dark-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff24c9b11eef9a090c3f8bb35d84c9fb33181385b2d635e6a3191dab9956e8dc -size 49402 diff --git a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-light-1-snap.png b/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-light-1-snap.png deleted file mode 100644 index 83dbdd3bdd..0000000000 --- a/packages/vkui/src/components/Tooltip/__image_snapshots__/tooltip-vkcom-webkit-light-1-snap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4770e823ba673e0b4a5f0bfd928e67054953f8f53a9401d24f3f4dd7ecf0a922 -size 55612 diff --git a/packages/vkui/src/components/TooltipBase/TooltipBase.module.css b/packages/vkui/src/components/TooltipBase/TooltipBase.module.css index 81e0b22621..4701bb0a47 100644 --- a/packages/vkui/src/components/TooltipBase/TooltipBase.module.css +++ b/packages/vkui/src/components/TooltipBase/TooltipBase.module.css @@ -1,9 +1,4 @@ .TooltipBase { - position: absolute; - inset-inline-start: 0; - inset-block-start: 0; - block-size: 100%; - inline-size: 100%; white-space: normal; } diff --git a/packages/vkui/src/components/TooltipBase/TooltipBase.tsx b/packages/vkui/src/components/TooltipBase/TooltipBase.tsx index 3fbbc38e2a..a7a617effc 100644 --- a/packages/vkui/src/components/TooltipBase/TooltipBase.tsx +++ b/packages/vkui/src/components/TooltipBase/TooltipBase.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; -import { HasRootRef } from '../../types'; +import type { HTMLAttributesWithRootRef } from '../../types'; import { DefaultIcon } from '../PopperArrow/DefaultIcon'; import { PopperArrow, type PopperArrowProps } from '../PopperArrow/PopperArrow'; import { RootComponent } from '../RootComponent/RootComponent'; @@ -16,7 +16,8 @@ const stylesAppearance = { inversion: styles['TooltipBase--appearance-inversion'], }; -export interface TooltipBaseProps extends HasRootRef { +export interface TooltipBaseProps + extends Omit, 'children'> { /** * Стиль отображения подсказки */ @@ -30,12 +31,9 @@ export interface TooltipBaseProps extends HasRootRef { */ header?: React.ReactNode; /** - * Отображать ли стрелку, указывающую на якорный элемент + * Для показа указателя, требуется передать хотя бы `coords` и `placement`. */ - withArrow?: boolean; - arrowCoords?: PopperArrowProps['coords']; - arrowPlacement?: PopperArrowProps['placement']; - getArrowRef?: PopperArrowProps['getRootRef']; + arrowProps?: Omit; /** * Пользовательская SVG иконка. * @@ -47,7 +45,7 @@ export interface TooltipBaseProps extends HasRootRef { * (см. https://github.com/VKCOM/VKUI/pull/4496). * 3. Убедитесь, что компонент принимает все валидные для SVG параметры. * 4. Убедитесь, что SVG и её элементы наследует цвет через `fill="currentColor"`. - * 5. Если стрелка наезжает на якорный элемент, то увеличьте значение параметра `offsetY`. + * 5. Если стрелка наезжает на якорный элемент, то увеличьте смещение между целевым и всплывающим элементами. */ ArrowIcon?: PopperArrowProps['Icon']; /** @@ -60,26 +58,22 @@ export interface TooltipBaseProps extends HasRootRef { * Передача `null` полностью сбрасывает установку `max-width` на элемент. */ maxWidth?: number | string | null; - floatingStyle?: React.CSSProperties; } /** * Низкоуровневый компонент для отрисовки тултипа. * Примеры использования и Readme можно найти в документации Tooltip * @see https://vkcom.github.io/VKUI/#/Tooltip + * @private */ export const TooltipBase = ({ appearance = 'accent', - withArrow = true, - arrowCoords, - arrowPlacement = 'top', - getArrowRef, - getRootRef, - floatingStyle, + arrowProps, ArrowIcon = DefaultIcon, text, header, maxWidth = TOOLTIP_MAX_WIDTH, + className, ...restProps }: TooltipBaseProps) => { return ( @@ -88,25 +82,23 @@ export const TooltipBase = ({ baseClassName={classNames( styles['TooltipBase'], appearance !== 'neutral' && stylesAppearance[appearance], + className, )} + role="tooltip" > -
- {withArrow && ( - - )} -
- {header && {header}} - {text && {text}} -
+ {arrowProps && ( + + )} +
+ {header && {header}} + {text && {text}}
); diff --git a/packages/vkui/src/index.ts b/packages/vkui/src/index.ts index 15278ee660..011d67fce1 100644 --- a/packages/vkui/src/index.ts +++ b/packages/vkui/src/index.ts @@ -108,6 +108,8 @@ export { ScreenSpinner } from './components/ScreenSpinner/ScreenSpinner'; export type { ScreenSpinnerProps } from './components/ScreenSpinner/ScreenSpinner'; export { Snackbar } from './components/Snackbar/Snackbar'; export type { SnackbarProps } from './components/Snackbar/Snackbar'; +export { Tooltip } from './components/Tooltip/Tooltip'; +export type { TooltipProps } from './components/Tooltip/Tooltip'; /** * Modals @@ -190,9 +192,9 @@ export { PullToRefresh } from './components/PullToRefresh/PullToRefresh'; export type { PullToRefreshProps } from './components/PullToRefresh/PullToRefresh'; export { Link } from './components/Link/Link'; export type { LinkProps } from './components/Link/Link'; -export { Tooltip } from './components/Tooltip/Tooltip'; -export type { TooltipProps } from './components/Tooltip/Tooltip'; -export { TooltipContainer } from './components/Tooltip/TooltipContainer'; +export { OnboardingTooltip } from './components/OnboardingTooltip/OnboardingTooltip'; +export type { OnboardingTooltipProps } from './components/OnboardingTooltip/OnboardingTooltip'; +export { OnboardingTooltipContainer } from './components/OnboardingTooltip/OnboardingTooltipContainer'; export { Counter } from './components/Counter/Counter'; export type { CounterProps } from './components/Counter/Counter'; export { UsersStack } from './components/UsersStack/UsersStack'; @@ -403,9 +405,6 @@ export type { TransitionContextProps } from './components/NavTransitionContext/N /** * Unstable */ -export { TextTooltip as unstable_TextTooltip } from './components/TextTooltip/TextTooltip'; -export type { TextTooltipProps as unstable_TextTooltipProps } from './components/TextTooltip/TextTooltip'; - export { ViewInfinite as unstable_ViewInfinite } from './components/View/ViewInfinite'; export type { ViewInfiniteProps as unstable_ViewInfiniteProps } from './components/View/ViewInfinite'; diff --git a/packages/vkui/src/lib/createPortal.ts b/packages/vkui/src/lib/createPortal.ts index 7c22350fc6..a685cfcd1b 100644 --- a/packages/vkui/src/lib/createPortal.ts +++ b/packages/vkui/src/lib/createPortal.ts @@ -1,9 +1,9 @@ import * as ReactDOM from 'react-dom'; import { getDocumentBody } from './dom'; -export const createPortal = ( +export const createPortal = ( children: React.ReactNode, - container?: Element | DocumentFragment, + container?: T, key?: null | string, ) => { const resolvedContainer = container ? container : getDocumentBody(); diff --git a/packages/vkui/src/lib/floating/functions.ts b/packages/vkui/src/lib/floating/functions.ts index b513ae4e69..851766e2f9 100644 --- a/packages/vkui/src/lib/floating/functions.ts +++ b/packages/vkui/src/lib/floating/functions.ts @@ -5,7 +5,7 @@ import type { Placement, PlacementWithAuto, UseFloatingData, -} from './types'; +} from './types/common'; export function checkIsNotAutoPlacement(placement: PlacementWithAuto): placement is Placement { return !placement.startsWith('auto'); @@ -46,3 +46,15 @@ export function convertFloatingDataToReactCSSProperties( } return styles; } + +export const getArrowCoordsByMiddlewareData = ( + middlewareData: UseFloatingData['middlewareData'], +) => { + const coords = { x: 0, y: 0 }; + if (middlewareData.arrow) { + const { x = 0, y = 0 } = middlewareData.arrow; + coords.x = x; + coords.y = y; + } + return coords; +}; diff --git a/packages/vkui/src/lib/floating/index.ts b/packages/vkui/src/lib/floating/index.ts index c86ceab7e8..78c78d0111 100644 --- a/packages/vkui/src/lib/floating/index.ts +++ b/packages/vkui/src/lib/floating/index.ts @@ -5,12 +5,15 @@ export type { PlacementWithAuto, AutoPlacementType, UseFloatingMiddleware, -} from './types'; +} from './types/common'; + +export type { FloatingComponentProps, FloatingContentRenderProp } from './types/component'; export { checkIsNotAutoPlacement, getAutoPlacementAlign, convertFloatingDataToReactCSSProperties, + getArrowCoordsByMiddlewareData, } from './functions'; export { diff --git a/packages/vkui/src/lib/floating/types.ts b/packages/vkui/src/lib/floating/types/common.ts similarity index 100% rename from packages/vkui/src/lib/floating/types.ts rename to packages/vkui/src/lib/floating/types/common.ts diff --git a/packages/vkui/src/lib/floating/types/component.ts b/packages/vkui/src/lib/floating/types/component.ts new file mode 100644 index 0000000000..9fae12d4ef --- /dev/null +++ b/packages/vkui/src/lib/floating/types/component.ts @@ -0,0 +1,55 @@ +import * as React from 'react'; +import type { UseFloatingMiddlewaresBootstrapOptions } from '../useFloatingMiddlewaresBootstrap'; +import type { + UseFloatingWithInteractionsProps, + UseFloatingWithInteractionsReturn, +} from '../useFloatingWithInteractions'; + +/** + * @private используйте алиасы, если для какого-то компонента нужно экспортировать тип + */ +export type FloatingContentRenderProp = ( + props: Pick, +) => React.ReactNode; + +/** + * Общий API для всплывающих элементов. + * + * @private + */ +export interface FloatingComponentProps + extends UseFloatingMiddlewaresBootstrapOptions, + Omit { + /** + * Содержимое всплывающего окна. + * + * При передаче контента в виде [render prop](https://react.dev/reference/react/cloneElement#passing-data-with-a-render-prop), + * в аргументе функции можно получить метод `onClose`, с помощью которого можно программно закрывать + * всплывающее окно. + */ + content?: React.ReactNode | FloatingContentRenderProp; + /** + * Целевой элемент. Всплывающее окно появится возле него. + * + * > ⚠️ Если это пользовательский компонент, то он должен: + * > 1. предоставлять параметры либо `getRootRef`, либо `ref` (cм. `React.forwardRef()`) для получения ссылки на DOM-узел; + * > 2. принимать DOM атрибуты и события. + */ + children?: React.ReactElement; + /** + * Нужно ли при навигации с клавиатуры авто-фокусироваться на всплывающий элемент. + */ + autoFocus?: boolean; + /** + * Нужно ли после закрытия всплывающего элемента возвращать фокус на предыдущий активный элемент. + */ + restoreFocus?: boolean; + /** + * Перебивает zIndex заданный по умолчанию. + */ + zIndex?: number | string; + /** + * По умолчанию используется document.body. + */ + usePortal?: boolean | HTMLElement | React.RefObject; +} diff --git a/packages/vkui/src/lib/floating/useFloatingMiddlewaresBootstrap/index.ts b/packages/vkui/src/lib/floating/useFloatingMiddlewaresBootstrap/index.ts index fcb700f8df..0dd79c0c03 100644 --- a/packages/vkui/src/lib/floating/useFloatingMiddlewaresBootstrap/index.ts +++ b/packages/vkui/src/lib/floating/useFloatingMiddlewaresBootstrap/index.ts @@ -9,7 +9,7 @@ import { sizeMiddleware, } from '../adapters'; import { checkIsNotAutoPlacement, getAutoPlacementAlign } from '../functions'; -import type { ArrowOptions, PlacementWithAuto, UseFloatingMiddleware } from '../types'; +import type { ArrowOptions, PlacementWithAuto, UseFloatingMiddleware } from '../types/common'; export interface UseFloatingMiddlewaresBootstrapOptions { /** @@ -46,7 +46,7 @@ export interface UseFloatingMiddlewaresBootstrapOptions { */ customMiddlewares?: UseFloatingMiddleware[]; /** - * Принудительно скрывает компонент если целевой элемент исчез. + * Принудительно скрывает компонент если целевой элемент вышел за область видимости. */ hideWhenReferenceHidden?: boolean; } diff --git a/packages/vkui/src/lib/floating/useFloatingWithInteractions/__snapshots__/useFloatingWithInteractions.test.tsx.snap b/packages/vkui/src/lib/floating/useFloatingWithInteractions/__snapshots__/useFloatingWithInteractions.test.tsx.snap index 079990a883..e056402f5e 100644 --- a/packages/vkui/src/lib/floating/useFloatingWithInteractions/__snapshots__/useFloatingWithInteractions.test.tsx.snap +++ b/packages/vkui/src/lib/floating/useFloatingWithInteractions/__snapshots__/useFloatingWithInteractions.test.tsx.snap @@ -7,6 +7,7 @@ exports[`useFloatingWithInteractions tests with snapshot should be hidden state "onMouseOver": [Function], "style": {}, }, + "middlewareData": {}, "onClose": [Function], "onEscapeKeyDown": undefined, "onRestoreFocus": [Function], @@ -49,6 +50,7 @@ exports[`useFloatingWithInteractions tests with snapshot should be shown state 1 "width": "max-content", }, }, + "middlewareData": {}, "onClose": [Function], "onEscapeKeyDown": [Function], "onRestoreFocus": [Function], @@ -91,6 +93,7 @@ exports[`useFloatingWithInteractions tests with snapshot should be shown state w "width": "max-content", }, }, + "middlewareData": {}, "onClose": [Function], "onEscapeKeyDown": undefined, "onRestoreFocus": [Function], @@ -132,6 +135,7 @@ exports[`useFloatingWithInteractions tests with snapshot should be shown state w "width": "max-content", }, }, + "middlewareData": {}, "onClose": [Function], "onEscapeKeyDown": [Function], "onRestoreFocus": [Function], @@ -163,6 +167,7 @@ exports[`useFloatingWithInteractions tests with snapshot should return default v "floatingProps": { "style": {}, }, + "middlewareData": {}, "onClose": [Function], "onEscapeKeyDown": undefined, "onRestoreFocus": [Function], diff --git a/packages/vkui/src/lib/floating/useFloatingWithInteractions/types.ts b/packages/vkui/src/lib/floating/useFloatingWithInteractions/types.ts index 2110ef8fb1..78b8d11200 100644 --- a/packages/vkui/src/lib/floating/useFloatingWithInteractions/types.ts +++ b/packages/vkui/src/lib/floating/useFloatingWithInteractions/types.ts @@ -1,5 +1,9 @@ -import type { Placement, UseFloatingRefs } from '../types'; -import type { UseFloatingMiddlewaresBootstrapOptions } from '../useFloatingMiddlewaresBootstrap'; +import type { + Placement, + UseFloatingData, + UseFloatingMiddleware, + UseFloatingRefs, +} from '../types/common'; export type InteractiveTriggerType = 'click' | 'hover' | 'focus'; @@ -17,11 +21,9 @@ export type ShownChangeReason = export type OnShownChange = (shown: boolean, reason?: ShownChangeReason) => void; -export interface UseFloatingWithInteractionsProps - extends Pick< - UseFloatingMiddlewaresBootstrapOptions, - 'placement' | 'offsetByMainAxis' | 'offsetByCrossAxis' - > { +export interface UseFloatingWithInteractionsProps { + placement?: Placement; + middlewares?: UseFloatingMiddleware[]; /** * Механика вызова всплывающего элемента. * @@ -46,6 +48,10 @@ export interface UseFloatingWithInteractionsProps * > Используется только для `trigger="hover"`. */ hoverDelay?: number | [number, number]; + /** + * При `trigger="hover"` закрывает всплывающий элемент при нажатии на целевой элемент. + */ + closeAfterClick?: boolean; /** * Блокирует изменение состояния. */ @@ -97,6 +103,7 @@ export interface UseFloatingWithInteractionsReturn; referenceProps: ReferenceProps; floatingProps: FloatingProps; + middlewareData: UseFloatingData['middlewareData']; onClose(this: void): void; onEscapeKeyDown?(this: void): void; onRestoreFocus(this: void): boolean; diff --git a/packages/vkui/src/lib/floating/useFloatingWithInteractions/useFloatingWithInteractions.ts b/packages/vkui/src/lib/floating/useFloatingWithInteractions/useFloatingWithInteractions.ts index 8e9be961ec..9e08310bd0 100644 --- a/packages/vkui/src/lib/floating/useFloatingWithInteractions/useFloatingWithInteractions.ts +++ b/packages/vkui/src/lib/floating/useFloatingWithInteractions/useFloatingWithInteractions.ts @@ -8,8 +8,7 @@ import { contains, getActiveElementByAnotherElement } from '../../dom'; import { useIsomorphicLayoutEffect } from '../../useIsomorphicLayoutEffect'; import { autoUpdateFloatingElement, useFloating } from '../adapters'; import { convertFloatingDataToReactCSSProperties } from '../functions'; -import { type UseFloatingOptions } from '../types'; -import { useFloatingMiddlewaresBootstrap } from '../useFloatingMiddlewaresBootstrap'; +import { type UseFloatingOptions } from '../types/common'; import { DEFAULT_TRIGGER } from './constants'; import type { FloatingProps, @@ -32,11 +31,11 @@ const whileElementsMounted: UseFloatingOptions['whileElementsMounted'] = (...arg export const useFloatingWithInteractions = ({ trigger = DEFAULT_TRIGGER, - // UseFloatingMiddlewaresBootstrapProps - placement: expectedPlacement = 'bottom', - offsetByMainAxis = 0, - offsetByCrossAxis = 0, + // UseFloating + placement: placementProp = 'bottom', + middlewares, hoverDelay = 0, + closeAfterClick = false, // disables disabled = false, @@ -70,6 +69,7 @@ export const useFloatingWithInteractions = const hasCSSAnimation = React.useRef(false); + const blockMouseEnterRef = React.useRef(false); const blockFocusRef = React.useRef(false); const blurTimeoutRef = React.useRef>(); @@ -81,14 +81,9 @@ export const useFloatingWithInteractions = const { triggerOnFocus, triggerOnClick, triggerOnHover } = useResolveTriggerType(trigger); // Библиотека `floating-ui` - const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ - placement: expectedPlacement, - offsetByMainAxis, - offsetByCrossAxis, - }); - const { placement, x, y, strategy, refs } = useFloating({ + const { placement, x, y, strategy, refs, middlewareData } = useFloating({ strategy: 'fixed', - placement: strictPlacement, + placement: placementProp, middleware: middlewares, whileElementsMounted, }); @@ -134,6 +129,7 @@ export const useFloatingWithInteractions = const handleBlurOnReference = useStableCallback((event: React.FocusEvent) => { blockFocusRef.current = false; + blockMouseEnterRef.current = false; if (!shownLocalState.shown) { clearTimeout(blurTimeoutRef.current); @@ -165,17 +161,23 @@ export const useFloatingWithInteractions = commitShownLocalState(!shownLocalState.shown, 'click'); }); + const handleClickOnReferenceForOnlyClose = useStableCallback(() => { + blockMouseEnterRef.current = true; + commitShownLocalState(false, 'click'); + }); + const handleMouseEnterOnBoth = useStableCallback(() => { showWithDelay.cancel(); hideWithDelay.cancel(); - if (!shownLocalState.shown) { + if (!blockMouseEnterRef.current && !shownLocalState.shown) { showWithDelay(); } }); const handleMouseLeaveOnBothForHoverAndFocusStates = useStableCallback(() => { blockFocusRef.current = false; + blockMouseEnterRef.current = false; if (triggerOnHover) { showWithDelay.cancel(); @@ -285,7 +287,13 @@ export const useFloatingWithInteractions = const floatingPropsRef = React.useRef({ style: {} }); if (shownFinalState) { - floatingPropsRef.current.style = convertFloatingDataToReactCSSProperties(strategy, x, y); + floatingPropsRef.current.style = convertFloatingDataToReactCSSProperties( + strategy, + x, + y, + undefined, + middlewareData, + ); if (disableInteractive) { floatingPropsRef.current.style.pointerEvents = 'none'; @@ -304,6 +312,10 @@ export const useFloatingWithInteractions = if (triggerOnHover) { referencePropsRef.current.onMouseOver = handleMouseEnterOnBoth; + if (closeAfterClick && !triggerOnClick) { + referencePropsRef.current.onClick = handleClickOnReferenceForOnlyClose; + } + if (!disableInteractive) { floatingPropsRef.current.onMouseOver = handleMouseEnterOnBoth; } @@ -329,6 +341,7 @@ export const useFloatingWithInteractions = refs, referenceProps: referencePropsRef.current, floatingProps: floatingPropsRef.current, + middlewareData, onClose: handleOnClose, // FocusTrap уже определяет нажатие на ESC, поэтому название события содержит конкретный код // кнопки вместо просто onKeyDown. diff --git a/styleguide/Components/ComplexType/ComplexTypeRenderer.js b/styleguide/Components/ComplexType/ComplexTypeRenderer.js index c528a3ebac..306038dbdb 100644 --- a/styleguide/Components/ComplexType/ComplexTypeRenderer.js +++ b/styleguide/Components/ComplexType/ComplexTypeRenderer.js @@ -1,11 +1,6 @@ import * as React from 'react'; import { Icon16ErrorCircleOutline } from '@vkontakte/icons'; -import { - classNames, - Text, - unstable_TextTooltip as TextTooltip, - useAdaptivityConditionalRender, -} from '@vkui'; +import { classNames, Text, Tooltip, useAdaptivityConditionalRender } from '@vkui'; import TypeRenderer from '../Type/TypeRenderer'; import './ComplexType.css'; @@ -16,7 +11,7 @@ export const ComplexTypeRenderer = ({ name, raw }) => { {sizeX.compact && {raw}} {sizeX.regular && ( - { {name} - + )} ); diff --git a/styleguide/Components/Setting/Setting.js b/styleguide/Components/Setting/Setting.js index 66398a6da8..50ba3e5bf5 100644 --- a/styleguide/Components/Setting/Setting.js +++ b/styleguide/Components/Setting/Setting.js @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useRef } from 'react'; import { Icon16Dropdown } from '@vkontakte/icons'; import { ActionSheet, ActionSheetItem, classNames, Headline, Link } from '@vkui'; import './Setting.css'; -import { Popover } from '@vkui/components/Popover/Popover'; +import { Tooltip } from '@vkui/components/Tooltip/Tooltip'; import { StyleGuideContext } from '../StyleGuide/StyleGuideRenderer'; export const Setting = ({ @@ -41,13 +41,9 @@ export const Setting = ({ weight="3" > {hint ? ( - {hint}
} - > + {labelJsx} - + ) : ( labelJsx )} diff --git a/styleguide/config.js b/styleguide/config.js index 0111292be9..85c8427436 100644 --- a/styleguide/config.js +++ b/styleguide/config.js @@ -183,8 +183,8 @@ const baseConfig = { name: 'Poppers', components: () => [ `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/Popover/Popover.tsx`, - `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/TextTooltip/TextTooltip.tsx`, `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/Tooltip/Tooltip.tsx`, + `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/OnboardingTooltip/OnboardingTooltip.tsx`, `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/Popper/Popper.tsx`, ], }, From 0236ee84043a6c0bf138736a0828c7cd5abe7fb0 Mon Sep 17 00:00:00 2001 From: Inomdzhon Mirdzhamolov Date: Wed, 6 Dec 2023 13:12:31 +0300 Subject: [PATCH 2/3] review: fix arrow offset property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Заменил `arrowCornerOffset` на `arrowOffset`. - Заменил `arrowCornerAbsoluteOffset` на `isStaticArrowOffset`. - Переименовал PopperArrow на FloatingArrow - Заменил UseFloatingMiddleware для смещения стрелки на параметр в FloatingArrow. --- .../DefaultIcon.tsx | 0 .../FloatingArrow.module.css} | 12 ++-- .../FloatingArrow.tsx} | 57 +++++++++++----- .../OnboardingTooltip.e2e-playground.tsx | 11 ++-- .../OnboardingTooltip.stories.tsx | 4 +- .../OnboardingTooltip/OnboardingTooltip.tsx | 66 +++++-------------- .../components/OnboardingTooltip/Readme.md | 4 +- ...ngtooltip-android-chromium-dark-1-snap.png | 4 +- ...gtooltip-android-chromium-light-1-snap.png | 4 +- ...boardingtooltip-ios-webkit-dark-1-snap.png | 4 +- ...oardingtooltip-ios-webkit-light-1-snap.png | 4 +- ...dingtooltip-vkcom-chromium-dark-1-snap.png | 4 +- ...ingtooltip-vkcom-chromium-light-1-snap.png | 4 +- ...rdingtooltip-vkcom-firefox-dark-1-snap.png | 4 +- ...dingtooltip-vkcom-firefox-light-1-snap.png | 4 +- ...ardingtooltip-vkcom-webkit-dark-1-snap.png | 4 +- ...rdingtooltip-vkcom-webkit-light-1-snap.png | 4 +- .../vkui/src/components/Popper/Popper.tsx | 18 ++--- .../components/TooltipBase/TooltipBase.tsx | 10 +-- 19 files changed, 109 insertions(+), 113 deletions(-) rename packages/vkui/src/components/{PopperArrow => FloatingArrow}/DefaultIcon.tsx (100%) rename packages/vkui/src/components/{PopperArrow/PopperArrow.module.css => FloatingArrow/FloatingArrow.module.css} (58%) rename packages/vkui/src/components/{PopperArrow/PopperArrow.tsx => FloatingArrow/FloatingArrow.tsx} (53%) diff --git a/packages/vkui/src/components/PopperArrow/DefaultIcon.tsx b/packages/vkui/src/components/FloatingArrow/DefaultIcon.tsx similarity index 100% rename from packages/vkui/src/components/PopperArrow/DefaultIcon.tsx rename to packages/vkui/src/components/FloatingArrow/DefaultIcon.tsx diff --git a/packages/vkui/src/components/PopperArrow/PopperArrow.module.css b/packages/vkui/src/components/FloatingArrow/FloatingArrow.module.css similarity index 58% rename from packages/vkui/src/components/PopperArrow/PopperArrow.module.css rename to packages/vkui/src/components/FloatingArrow/FloatingArrow.module.css index 3575611ab9..50a132bb9c 100644 --- a/packages/vkui/src/components/PopperArrow/PopperArrow.module.css +++ b/packages/vkui/src/components/FloatingArrow/FloatingArrow.module.css @@ -1,25 +1,25 @@ -.PopperArrow { +.FloatingArrow { position: absolute; } -.PopperArrow__in { +.FloatingArrow__in { content: ''; display: block; - /* см. Примечание 1 в PopperArrow.tsx. */ + /* см. Примечание 1 в FloatingArrow.tsx. */ transform: translateY(1px); } -.PopperArrow--placement-right { +.FloatingArrow--placement-right { transform: rotate(90deg) translate(50%, -50%); transform-origin: right; } -.PopperArrow--placement-bottom { +.FloatingArrow--placement-bottom { transform: rotate(180deg); } -.PopperArrow--placement-left { +.FloatingArrow--placement-left { transform: rotate(-90deg) translate(-50%, -50%); transform-origin: left; } diff --git a/packages/vkui/src/components/PopperArrow/PopperArrow.tsx b/packages/vkui/src/components/FloatingArrow/FloatingArrow.tsx similarity index 53% rename from packages/vkui/src/components/PopperArrow/PopperArrow.tsx rename to packages/vkui/src/components/FloatingArrow/FloatingArrow.tsx index 210a19b492..f230b841f5 100644 --- a/packages/vkui/src/components/PopperArrow/PopperArrow.tsx +++ b/packages/vkui/src/components/FloatingArrow/FloatingArrow.tsx @@ -3,7 +3,7 @@ import { classNames } from '@vkontakte/vkjs'; import type { Placement } from '../../lib/floating'; import type { HasDataAttribute, HTMLAttributesWithRootRef } from '../../types'; import { DefaultIcon } from './DefaultIcon'; -import styles from './PopperArrow.module.css'; +import styles from './FloatingArrow.module.css'; export type Coords = { x?: number; @@ -11,14 +11,22 @@ export type Coords = { }; const placementClassNames = { - right: styles['PopperArrow--placement-right'], - bottom: styles['PopperArrow--placement-bottom'], - left: styles['PopperArrow--placement-left'], + right: styles['FloatingArrow--placement-right'], + bottom: styles['FloatingArrow--placement-bottom'], + left: styles['FloatingArrow--placement-left'], }; -export interface PopperArrowProps +export interface FloatingArrowProps extends HTMLAttributesWithRootRef, HasDataAttribute { + /** + * Сдвиг стрелки относительно текущих координат. + */ + offset?: number; + /** + * Включает абсолютное смещение по `offset`. + */ + isStaticOffset?: boolean; coords?: Coords; placement?: Placement; iconStyle?: React.CSSProperties; @@ -27,9 +35,11 @@ export interface PopperArrowProps } /** - * TODO [>=6.1.0] Переименовать в FloatingArrow + * @private */ -export const PopperArrow = ({ +export const FloatingArrow = ({ + offset, + isStaticOffset, coords, iconStyle, iconClassName, @@ -37,20 +47,25 @@ export const PopperArrow = ({ getRootRef, Icon = DefaultIcon, ...restProps -}: PopperArrowProps) => { - const [arrowPlacement, arrowStyles] = getArrowPositionData(placement, coords); +}: FloatingArrowProps) => { + const [arrowPlacement, arrowStyles] = getArrowPositionData( + placement, + coords, + offset, + isStaticOffset, + ); return (
- +
); }; @@ -58,20 +73,32 @@ export const PopperArrow = ({ function getArrowPositionData( placement: Placement, coords: Coords = { x: 0, y: 0 }, + offset = 0, + isStaticOffset = false, ): [undefined | 'right' | 'bottom' | 'left', React.CSSProperties] { + const withOffset = (isVerticalPlacement: boolean) => { + const parsedCoords = { x: coords.x || 0, y: coords.y || 0 }; + + if (isVerticalPlacement) { + return isStaticOffset ? offset : parsedCoords.y + offset; + } else { + return isStaticOffset ? offset : parsedCoords.x + offset; + } + }; + if (placement.startsWith('top')) { return [ 'bottom', { top: '100%', - left: coords.x, + left: withOffset(false), }, ]; } else if (placement.startsWith('right')) { return [ 'left', { - top: coords.y, + top: withOffset(true), left: 0, }, ]; @@ -80,14 +107,14 @@ function getArrowPositionData( undefined, { bottom: '100%', - left: coords.x, + left: withOffset(false), }, ]; } else { return [ 'right', { - top: coords.y, + top: withOffset(true), right: 0, }, ]; diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx index a3c7ed85f7..9928eaaac1 100644 --- a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.e2e-playground.tsx @@ -19,12 +19,13 @@ export const OnboardingTooltipPlayground = (props: ComponentPlaygroundProps) => placement: ['top-start', 'top-end', 'bottom-start', 'bottom-end'], }, { - placement: ['top-start'], - arrowCornerOffset: [5, -5], + placement: ['top'], + arrowOffset: [15, -15], }, { - placement: ['top-start'], - arrowCornerAbsoluteOffset: [10, -1], + placement: ['top'], + arrowOffset: [10, -1], + isStaticArrowOffset: [true], }, ]} > @@ -40,7 +41,7 @@ export const OnboardingTooltipPlayground = (props: ComponentPlaygroundProps) => justifyContent: 'center', }} > - +
Tooltip target
diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx index c6c7405186..5168c3eeb0 100644 --- a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.stories.tsx @@ -104,7 +104,7 @@ export const ShowCase: Story = { text="Теперь у нас появились аватарки в списках. Правда круто?" shown={tooltip3} onClose={() => setTooltip3(false)} - arrowCornerOffset={-6} + arrowOffset={-6} >
@@ -150,7 +150,7 @@ export const WithOnboardingTooltipContainer: Story = {
- +
diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx index b7ea684102..6e11d06c96 100644 --- a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltip.tsx @@ -7,15 +7,14 @@ import { autoUpdateFloatingElement, convertFloatingDataToReactCSSProperties, type FloatingComponentProps, - type PlacementWithAuto, useFloating, - type UseFloatingMiddleware, useFloatingMiddlewaresBootstrap, } from '../../lib/floating'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { warnOnce } from '../../lib/warnOnce'; +import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING } from '../FloatingArrow/DefaultIcon'; +import { FloatingArrowProps } from '../FloatingArrow/FloatingArrow'; import { useNavTransition } from '../NavTransitionContext/NavTransitionContext'; -import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING } from '../PopperArrow/DefaultIcon'; import { TOOLTIP_MAX_WIDTH, TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase'; import { onboardingTooltipContainerAttr } from './OnboardingTooltipContainer'; import styles from './OnboardingTooltip.module.css'; @@ -36,17 +35,21 @@ type AllowedFloatingComponentProps = Pick< type AllowedTooltipBaseProps = Omit; -export interface OnboardingTooltipProps - extends AllowedFloatingComponentProps, - AllowedTooltipBaseProps { +type AllowedFloatingArrowProps = { /** - * Сдвиг стрелочки относительно центра дочернего элемента. + * Сдвиг стрелки относительно текущих координат. */ - arrowCornerOffset?: number; + arrowOffset?: FloatingArrowProps['offset']; /** - * Сдвиг стрелочки относительно ширины тултипа + * Включает абсолютное смещение по `arrowOffset`. */ - arrowCornerAbsoluteOffset?: number; + isStaticArrowOffset?: FloatingArrowProps['isStaticOffset']; +}; + +export interface OnboardingTooltipProps + extends AllowedFloatingComponentProps, + AllowedTooltipBaseProps, + AllowedFloatingArrowProps { /** * Callback, который вызывается при клике по любому месту в пределах экрана. */ @@ -64,8 +67,8 @@ export const OnboardingTooltip = ({ arrowHeight = DEFAULT_ARROW_HEIGHT, offsetByMainAxis = 0, offsetByCrossAxis = 0, - arrowCornerOffset = 0, - arrowCornerAbsoluteOffset, + arrowOffset = 0, + isStaticArrowOffset = false, onClose, placement: placementProp = 'bottom-start', maxWidth = TOOLTIP_MAX_WIDTH, @@ -82,10 +85,6 @@ export const OnboardingTooltip = ({ const [positionStrategy, setPositionStrategy] = React.useState<'fixed' | 'absolute'>('absolute'); const shown = shownProp && tooltipContainer && !entering; - const customMiddlewares = React.useMemo( - () => [getArrowOffsetMiddleware(arrowCornerOffset, arrowCornerAbsoluteOffset)], - [arrowCornerAbsoluteOffset, arrowCornerOffset], - ); const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ placement: placementProp, offsetByMainAxis, @@ -94,7 +93,6 @@ export const OnboardingTooltip = ({ arrow: true, arrowHeight, arrowPadding, - customMiddlewares, }); const { x: floatingDataX, @@ -134,6 +132,8 @@ export const OnboardingTooltip = ({ style={floatingStyle} maxWidth={maxWidth} arrowProps={{ + offset: arrowOffset, + isStaticOffset: isStaticArrowOffset, coords: arrowCoords, placement: resolvedPlacement, getRootRef: setArrowRef, @@ -187,35 +187,3 @@ export const OnboardingTooltip = ({ ); }; - -function getArrowOffsetMiddleware( - cornerOffset: number, - cornerAbsoluteOffset?: number, -): UseFloatingMiddleware { - return { - name: 'arrowOffset', - fn({ placement, middlewareData }) { - if (!middlewareData.arrow) { - return Promise.resolve({}); - } - - const isVerticalPlacement = (placement: PlacementWithAuto) => - placement.startsWith('top') || placement.startsWith('bottom'); - - if (isVerticalPlacement(placement)) { - if (cornerAbsoluteOffset !== undefined) { - middlewareData.arrow.x = cornerAbsoluteOffset; - } else if (middlewareData.arrow.x !== undefined) { - middlewareData.arrow.x += cornerOffset; - } - } else { - if (cornerAbsoluteOffset !== undefined) { - middlewareData.arrow.y = cornerAbsoluteOffset; - } else if (middlewareData.arrow.y !== undefined) { - middlewareData.arrow.y += cornerOffset; - } - } - return Promise.resolve({}); - }, - }; -} diff --git a/packages/vkui/src/components/OnboardingTooltip/Readme.md b/packages/vkui/src/components/OnboardingTooltip/Readme.md index 9b8ea18e1b..6cab100489 100644 --- a/packages/vkui/src/components/OnboardingTooltip/Readme.md +++ b/packages/vkui/src/components/OnboardingTooltip/Readme.md @@ -90,7 +90,7 @@ const Example = () => { text="Теперь у нас появились аватарки в списках. Правда круто?" shown={tooltip3} onClose={() => setTooltip3(false)} - arrowCornerOffset={-6} + arrowOffset={-6} >
@@ -142,7 +142,7 @@ const Example = () => {
- +
diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png index 7f468e2781..f7e46cd222 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed6e7d694b5475b1075211af333e496185a3aa4131fe696ca52ee5a440c8318b -size 53977 +oid sha256:e2436158efc0b38995ced8994b1650b880d64f7308292eec7f226b3515a23b83 +size 61713 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png index ad1a8fb45c..4bc08552ca 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-android-chromium-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:875d8c9cb21fdd373a5b263002929b112bb033a4cf99e0d75a4b49d71d7e438d -size 60931 +oid sha256:2ffd4fc2f5b90c9d59d1930fdf94c5a09baa931342df2b2de0b0999a64e6cfbe +size 68451 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png index 9df8407c10..fbde11b728 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee33df886a72fc1a6d1e10249eb162a38ac14f2991b2d772b3b938f2701b4e9e -size 50316 +oid sha256:7adac8bdcbe631c6f6cce5dd6d2a6553aab8b3ff93cefff86f5b5b525735d456 +size 57976 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png index 85c91cbe48..9aa1be6438 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-ios-webkit-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc320751bbfd8c49b642f87dd6f627b037c51a61fc980d667a021fdb1f7294f3 -size 57079 +oid sha256:44753c2b0c39f3391583b33879ff3170e899ebe186e5293ad58a6dc00e8ec35d +size 65327 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png index 7084cbdd89..d2f8ccfa29 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a224376fc4e5507ea9cc2b79231ca3c2be1115a9478e6ab8fb2c026a5c074ccc -size 54282 +oid sha256:627cc3e2a8ce71f69d06023ebf3aa461676b89459c23c0f3a545c674427d2aff +size 61016 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png index e8a370bd4e..7ff69f5fe3 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-chromium-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6d048deb1ff6427dab6a56ce3d759a3afa98bb72147bda4f5d336a97cb6a946 -size 61130 +oid sha256:e63d12702f855a5594ada78097785aa52258e3997c169d7cb0f8c906e4f91e38 +size 68113 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png index ef2b50d231..53bf22638c 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f6c7e0e11e7993afb21f93eb470debbacc7656949e20fa39d7df29fd95afefb -size 69529 +oid sha256:0f5addc93a79e61bd8fd5722e82ee7da3d4a55670cb5f41000a5d6b3bfca5465 +size 82542 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png index 181d9c11ff..3242ae9ff1 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-firefox-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb1515df4b88f5afa1a11910cc7696be3ecff9589ee5a134423e74ba4c66751b -size 81784 +oid sha256:571ae9140c99c4f49401381f8c8ce3290aceafc7e8031c9501f5265ae7cdcf55 +size 93813 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png index 182fe665d9..1d8a0af2c5 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-dark-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d22fa9d03637eddd77dd4dd254d2929c336a1dcc4fbd03d627a5a7a938baec7 -size 49594 +oid sha256:763421661592e69aa4b3da57fa58a5d8b21926431d2f7f49bdfcaac5783716b5 +size 56627 diff --git a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png index 03c0e1e1b1..6d39b7887e 100644 --- a/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png +++ b/packages/vkui/src/components/OnboardingTooltip/__image_snapshots__/onboardingtooltip-vkcom-webkit-light-1-snap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21dbb5b5892d7a4e529a01a440eeac1b17be11d7c323687d38b18e651f3d526b -size 55770 +oid sha256:d9053f395b99a0268739498845521480826e4f5bb2d51d1071bfe71dfb7c7a06 +size 63401 diff --git a/packages/vkui/src/components/Popper/Popper.tsx b/packages/vkui/src/components/Popper/Popper.tsx index 5d4ba51457..7da7b4ea52 100644 --- a/packages/vkui/src/components/Popper/Popper.tsx +++ b/packages/vkui/src/components/Popper/Popper.tsx @@ -17,16 +17,16 @@ import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING, DefaultIcon, -} from '../PopperArrow/DefaultIcon'; +} from '../FloatingArrow/DefaultIcon'; import { - PopperArrow, - type PopperArrowProps as PopperArrowPropsPrivate, -} from '../PopperArrow/PopperArrow'; + FloatingArrow, + type FloatingArrowProps as FloatingArrowPropsPrivate, +} from '../FloatingArrow/FloatingArrow'; import { RootComponent } from '../RootComponent/RootComponent'; import styles from './Popper.module.css'; -export type PopperArrowProps = Omit< - PopperArrowPropsPrivate, +export type FloatingArrowProps = Omit< + FloatingArrowPropsPrivate, 'getRootRef' | 'coords' | 'placement' | 'Icon' >; @@ -56,7 +56,7 @@ export interface PopperCommonProps /** * Позволяет набросить на стрелку пользовательские атрибуты. */ - arrowProps?: PopperArrowProps; + arrowProps?: FloatingArrowProps; /** * Пользовательская SVG иконка. * @@ -70,7 +70,7 @@ export interface PopperCommonProps * 4. Убедитесь, что компонент принимает все валидные для SVG параметры. * 5. Убедитесь, что SVG и её элементы наследует цвет через `fill="currentColor"`. */ - ArrowIcon?: PopperArrowPropsPrivate['Icon']; + ArrowIcon?: FloatingArrowPropsPrivate['Icon']; /** * Подписывается на изменение геометрии `targetRef`, чтобы пересчитать свою позицию. */ @@ -184,7 +184,7 @@ export const Popper = ({ }} > {arrow && ( - ; + arrowProps?: Omit; /** * Пользовательская SVG иконка. * @@ -47,7 +47,7 @@ export interface TooltipBaseProps * 4. Убедитесь, что SVG и её элементы наследует цвет через `fill="currentColor"`. * 5. Если стрелка наезжает на якорный элемент, то увеличьте смещение между целевым и всплывающим элементами. */ - ArrowIcon?: PopperArrowProps['Icon']; + ArrowIcon?: FloatingArrowProps['Icon']; /** * Пользовательские css-классы, будут добавлены на root-элемент */ @@ -87,7 +87,7 @@ export const TooltipBase = ({ role="tooltip" > {arrowProps && ( - Date: Wed, 6 Dec 2023 13:20:51 +0300 Subject: [PATCH 3/3] review(Tooltip): reword docs --- packages/vkui/src/components/Tooltip/Tooltip.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vkui/src/components/Tooltip/Tooltip.tsx b/packages/vkui/src/components/Tooltip/Tooltip.tsx index d8332651d7..b025a4cee4 100644 --- a/packages/vkui/src/components/Tooltip/Tooltip.tsx +++ b/packages/vkui/src/components/Tooltip/Tooltip.tsx @@ -36,10 +36,10 @@ type AllowedTooltipBaseProps = Omit; export interface TooltipProps extends AllowedFloatingComponentProps, AllowedTooltipBaseProps { /** - * Если передан, то тултип перейдёт контролируемый компонент. - * Используйте совместно с `onShawnChange`. + * Передача `boolean` позволяет контролировать состояния показа и скрытия вручную. Используйте + * совместно с `onShawnChange`. * - * > Если нужно показать тултип лишь при первом рендере, то лучше используйте `defaultShown`. + * > Если нужно разово инициировать показ тултипа при первом рендере, то используйте `defaultShown`. */ shown?: boolean; /** @@ -127,7 +127,7 @@ export const Tooltip = ({ shown: shownProp, onShownChange, placement: strictPlacement, - trigger: shownProp !== undefined ? 'manual' : ['hover', 'focus'], + trigger: ['hover', 'focus'], hoverDelay, closeAfterClick: !disableCloseAfterClick, disableInteractive: !enableInteractive,