Skip to content

Commit

Permalink
fix: Add popper changes for v6 (#6482)
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackySoul authored and actions-user committed Jan 29, 2024
1 parent 4ba1b01 commit e9b3abb
Show file tree
Hide file tree
Showing 19 changed files with 246 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { TooltipContainer } from '@vkontakte/vkui';
import React from 'react';

const App = () => {
return (
<React.Fragment>
<TooltipContainer>Tooltip</TooltipContainer>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const App = () => {
<Tooltip alignX="right" alignY="top">target</Tooltip>

<Tooltip alignX="right" alignY="top" placement="auto">target</Tooltip>

<Tooltip arrow></Tooltip>

<Tooltip arrow={false}>target</Tooltip>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`tooltip-container transforms correctly 1`] = `
"import { OnboardingTooltipContainer } from '@vkontakte/vkui';
import React from 'react';
const App = () => {
return (
<React.Fragment>
<OnboardingTooltipContainer>Tooltip</OnboardingTooltipContainer>
</React.Fragment>
);
};"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const App = () => {
<OnboardingTooltip placement="top-end">target</OnboardingTooltip>
<OnboardingTooltip placement="auto">target</OnboardingTooltip>
<OnboardingTooltip></OnboardingTooltip>
<OnboardingTooltip disableArrow>target</OnboardingTooltip>
</React.Fragment>
);
};"
Expand Down
12 changes: 12 additions & 0 deletions packages/codemods/src/transforms/__tests__/tooltip-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
jest.autoMockOff();

import { defineSnapshotTestFromFixture } from '../../testHelpers/testHelper';

const name = 'tooltip-container';
const fixtures = ['basic'] as const;

describe(name, () => {
fixtures.forEach((test) =>
defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`),
);
});
51 changes: 51 additions & 0 deletions packages/codemods/src/transforms/tooltip-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { API, FileInfo } from 'jscodeshift';
import { getImportInfo } from '../codemod-helpers';
import type { JSCodeShiftOptions } from '../types';

export const parser = 'tsx';

const componentName = 'TooltipContainer';
const componentNameTo = 'OnboardingTooltipContainer';

export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) {
const { alias } = options;
const j = api.jscodeshift;
const source = j(file.source);
const { localName } = getImportInfo(j, file, componentName, alias);
let needRename = true;

// подменяем импорт
source
.find(j.ImportDeclaration)
.filter((path) => path.node.source.value === alias)
.find(j.ImportSpecifier, { imported: { name: componentName } })
.forEach((path) => {
j(path).replaceWith((path) => {
if (path.node.local && path.node.local.name !== path.node.imported.name) {
needRename = false;
}
return j.importSpecifier(
j.jsxIdentifier(componentNameTo),
needRename ? null : path.node.local,
);
});
});

source.findJSXElements(localName).forEach((element) => {
// меняем название компонента в JSX на переименованный в импорте (если нужно)
j(element).replaceWith((path) => {
const renamedLocalName = needRename ? componentNameTo : localName;
return j.jsxElement(
j.jsxOpeningElement(
j.jsxIdentifier(renamedLocalName),
path.node.openingElement.attributes,
path.node.closingElement ? false : true,
),
path.node.closingElement ? j.jsxClosingElement(j.jsxIdentifier(renamedLocalName)) : null,
path.node.children,
);
});
});

return source.toSource();
}
3 changes: 3 additions & 0 deletions packages/codemods/src/transforms/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type AttributeManipulator,
createAttributeManipulator,
getImportInfo,
swapBooleanValue,
} from '../codemod-helpers';
import type { JSCodeShiftOptions } from '../types';

Expand Down Expand Up @@ -66,6 +67,8 @@ export default function transformer(file: FileInfo, api: API, options: JSCodeShi
const attributeReplacer = createAttributeManipulator(ATTRIBUTE_REPLACER, api);
let needRename = true;

swapBooleanValue(api, source, localName, 'arrow', 'disableArrow');

// подменяем импорт
source
.find(j.ImportDeclaration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 { OnboardingTooltip, OnboardingTooltipProps } from './OnboardingTooltip';
import { OnboardingTooltipContainer } from './OnboardingTooltipContainer';

const renderTooltip = async (jsx: ReactElement) => {
Expand Down Expand Up @@ -103,4 +103,26 @@ describe(OnboardingTooltip, () => {
expect(ref).toHaveBeenCalledWith(screen.getByTestId('xxx'));
});
});

it('should call onPlacementChange', async () => {
const onPlacementChange = jest.fn();

const Fixture = (props: OnboardingTooltipProps) => (
<OnboardingTooltipContainer data-testid="container">
<OnboardingTooltip shown text="text" {...props}>
<div data-testid="xxx" />
</OnboardingTooltip>
</OnboardingTooltipContainer>
);

const result = render(<Fixture placement="bottom" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).not.toHaveBeenCalled();

result.rerender(<Fixture placement="auto" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).toHaveBeenCalledWith('top');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type FloatingComponentProps,
useFloating,
useFloatingMiddlewaresBootstrap,
usePlacementChangeCallback,
} from '../../lib/floating';
import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
import { warnOnce } from '../../lib/warnOnce';
Expand All @@ -31,6 +32,7 @@ type AllowedFloatingComponentProps = Pick<
| 'offsetByCrossAxis'
| 'shown'
| 'children'
| 'onPlacementChange'
>;

type AllowedTooltipBaseProps = Omit<TooltipBaseProps, 'arrowProps'>;
Expand All @@ -50,6 +52,10 @@ export interface OnboardingTooltipProps
extends AllowedFloatingComponentProps,
AllowedTooltipBaseProps,
AllowedFloatingArrowProps {
/**
* Скрывает стрелку, указывающую на якорный элемент.
*/
disableArrow?: boolean;
/**
* Callback, который вызывается при клике по любому месту в пределах экрана.
*/
Expand All @@ -74,6 +80,8 @@ export const OnboardingTooltip = ({
maxWidth = TOOLTIP_MAX_WIDTH,
style: styleProp,
getRootRef,
disableArrow = false,
onPlacementChange,
...restProps
}: OnboardingTooltipProps) => {
const generatedId = React.useId();
Expand All @@ -90,7 +98,7 @@ export const OnboardingTooltip = ({
offsetByMainAxis,
offsetByCrossAxis,
arrowRef,
arrow: true,
arrow: !disableArrow,
arrowHeight,
arrowPadding,
});
Expand All @@ -111,6 +119,8 @@ export const OnboardingTooltip = ({
'aria-describedby': shown ? tooltipId : undefined,
});

usePlacementChangeCallback(resolvedPlacement, onPlacementChange);

let tooltip: React.ReactPortal | null = null;
if (shown) {
const floatingStyle = convertFloatingDataToReactCSSProperties(
Expand All @@ -131,13 +141,17 @@ export const OnboardingTooltip = ({
getRootRef={tooltipRef}
style={floatingStyle}
maxWidth={maxWidth}
arrowProps={{
offset: arrowOffset,
isStaticOffset: isStaticArrowOffset,
coords: arrowCoords,
placement: resolvedPlacement,
getRootRef: setArrowRef,
}}
arrowProps={
disableArrow
? undefined
: {
offset: arrowOffset,
isStaticOffset: isStaticArrowOffset,
coords: arrowCoords,
placement: resolvedPlacement,
getRootRef: setArrowRef,
}
}
/>
<div className={styles['OnboardingTooltip__overlay']} onClickCapture={onClose} />
</>,
Expand Down
20 changes: 20 additions & 0 deletions packages/vkui/src/components/Popover/Popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,24 @@ describe(Popover, () => {
await waitForFloatingPosition();
expect(result.getByTestId('target')).toHaveAttribute('aria-expanded', 'false');
});

it('should call onPlacementChange', async () => {
const onPlacementChange = jest.fn();

const Fixture = (props: PopoverProps) => (
<Popover defaultShown {...props}>
<div>Target</div>
</Popover>
);

const result = render(<Fixture placement="bottom" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).not.toHaveBeenCalled();

result.rerender(<Fixture placement="auto" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).toHaveBeenCalledWith('top');
});
});
5 changes: 5 additions & 0 deletions packages/vkui/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type OnShownChange,
useFloatingMiddlewaresBootstrap,
useFloatingWithInteractions,
usePlacementChangeCallback,
} from '../../lib/floating';
import type { HTMLAttributesWithRootRef } from '../../types';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
Expand All @@ -30,6 +31,7 @@ export type PopoverContentRenderProp = FloatingContentRenderProp;
type AllowedFloatingComponentProps = Pick<
FloatingComponentProps,
| 'placement'
| 'onPlacementChange'
| 'trigger'
| 'content'
| 'hoverDelay'
Expand Down Expand Up @@ -75,6 +77,7 @@ export interface PopoverProps
export const Popover = ({
// UsePopoverProps
placement: expectedPlacement = 'bottom-start',
onPlacementChange,
trigger = 'click',
content,
hoverDelay = 150,
Expand Down Expand Up @@ -139,6 +142,8 @@ export const Popover = ({
onShownChange,
});

usePlacementChangeCallback(placement, onPlacementChange);

const [, child] = usePatchChildren<HTMLDivElement>(
children,
injectAriaExpandedPropByRole(referenceProps, shown, role),
Expand Down
22 changes: 5 additions & 17 deletions packages/vkui/src/components/Popper/Popper.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import * as React from 'react';
import { useExternRef } from '../../hooks/useExternRef';
import { usePrevious } from '../../hooks/usePrevious';
import {
autoUpdateFloatingElement,
convertFloatingDataToReactCSSProperties,
type FloatingComponentProps,
type Placement,
useFloating,
useFloatingMiddlewaresBootstrap,
usePlacementChangeCallback,
type VirtualElement,
} from '../../lib/floating';
import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
Expand Down Expand Up @@ -48,6 +47,7 @@ type AllowedFloatingComponentProps = Pick<
| 'zIndex'
| 'usePortal'
| 'customMiddlewares'
| 'onPlacementChange'
>;

export interface PopperCommonProps
Expand Down Expand Up @@ -75,11 +75,6 @@ export interface PopperCommonProps
* Подписывается на изменение геометрии `targetRef`, чтобы пересчитать свою позицию.
*/
autoUpdateOnTargetResize?: boolean;
/**
* В зависимости от области видимости, позиция может смениться на более оптимальную,
* чтобы Popper вместился в эту область видимости.
*/
onPlacementChange?(placement: Placement): void;
}

export interface PopperProps extends PopperCommonProps {
Expand Down Expand Up @@ -149,6 +144,9 @@ export const Popper = ({
});
},
});

usePlacementChangeCallback(resolvedPlacement, onPlacementChange);

const { arrow: arrowCoords } = middlewareData;

const handleRootRef = useExternRef<HTMLDivElement>(refs.setFloating, getRootRef);
Expand All @@ -157,16 +155,6 @@ export const Popper = ({
refs.setReference('current' in targetRef ? targetRef.current : targetRef);
}, [refs.setReference, targetRef]);

const prevResolvedPlacement = usePrevious(resolvedPlacement);
useIsomorphicLayoutEffect(() => {
if (prevResolvedPlacement === undefined || !onPlacementChange) {
return;
}
if (prevResolvedPlacement !== resolvedPlacement) {
onPlacementChange(resolvedPlacement);
}
}, [prevResolvedPlacement, resolvedPlacement, onPlacementChange]);

const dropdown = (
<RootComponent
{...restProps}
Expand Down
25 changes: 23 additions & 2 deletions packages/vkui/src/components/Tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import * as React from 'react';
import { baselineComponent } from '../../testing/utils';
import { Tooltip } from './Tooltip';
import { render } from '@testing-library/react';
import { baselineComponent, waitForFloatingPosition } from '../../testing/utils';
import { Tooltip, TooltipProps } from './Tooltip';

describe(Tooltip, () => {
baselineComponent((props) => (
<Tooltip shown text="test" {...props}>
<div>Target</div>
</Tooltip>
));

it('should call onPlacementChange', async () => {
const onPlacementChange = jest.fn();

const Fixture = (props: TooltipProps) => (
<Tooltip defaultShown {...props}>
<div>Target</div>
</Tooltip>
);

const result = render(<Fixture placement="bottom" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).not.toHaveBeenCalled();

result.rerender(<Fixture placement="auto" onPlacementChange={onPlacementChange} />);
await waitForFloatingPosition();

expect(onPlacementChange).toHaveBeenCalledWith('top');
});
});
Loading

0 comments on commit e9b3abb

Please sign in to comment.