Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Popper): add prop getFloatingElementHiddenStyles #8126

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ export const OnboardingTooltip = ({

let tooltip: React.ReactPortal | null = null;
if (shown) {
const floatingStyle = convertFloatingDataToReactCSSProperties(
positionStrategy,
floatingDataX,
floatingDataY,
);
const floatingStyle = convertFloatingDataToReactCSSProperties({
strategy: positionStrategy,
x: floatingDataX,
y: floatingDataY,
});

tooltip = createPortal(
<>
Expand Down
1 change: 1 addition & 0 deletions packages/vkui/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type AllowedFloatingComponentProps = Pick<
| 'usePortal'
| 'sameWidth'
| 'hideWhenReferenceHidden'
| 'getFloatingElementHiddenStyles'
| 'disabled'
| 'disableInteractive'
| 'disableCloseOnClickOutside'
Expand Down
2 changes: 2 additions & 0 deletions packages/vkui/src/components/Popover/usePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const usePopover = <ElementType extends HTMLElement = HTMLElement>({
offsetByCrossAxis = 0,
sameWidth,
hideWhenReferenceHidden,
getFloatingElementHiddenStyles,
disabled,
disableInteractive,
disableCloseOnClickOutside,
Expand Down Expand Up @@ -108,6 +109,7 @@ export const usePopover = <ElementType extends HTMLElement = HTMLElement>({
trigger,
strategy,
hoverDelay,
getFloatingElementHiddenStyles,
closeAfterClick,
disabled,
disableInteractive,
Expand Down
15 changes: 9 additions & 6 deletions packages/vkui/src/components/Popper/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type AllowedFloatingComponentProps = Pick<
| 'onShownChange'
| 'defaultShown'
| 'hideWhenReferenceHidden'
| 'getFloatingElementHiddenStyles'
| 'sameWidth'
| 'zIndex'
| 'usePortal'
Expand Down Expand Up @@ -100,6 +101,7 @@ export const Popper = ({
arrowPadding = DEFAULT_ARROW_PADDING,
customMiddlewares,
disableFlipMiddleware = false,
getFloatingElementHiddenStyles,

// UseFloatingProps
autoUpdateOnTargetResize = false,
Expand Down Expand Up @@ -175,13 +177,14 @@ export const Popper = ({
style={mergeStyle(dropdownStyle, style)}
baseClassName={styles.host}
getRootRef={handleRootRef}
baseStyle={convertFloatingDataToReactCSSProperties(
floatingPositionStrategy,
floatingDataX,
floatingDataY,
sameWidth ? null : undefined,
baseStyle={convertFloatingDataToReactCSSProperties({
strategy: floatingPositionStrategy,
x: floatingDataX,
y: floatingDataY,
initialWidth: sameWidth ? null : undefined,
middlewareData,
)}
getFloatingElementHiddenStyles,
})}
>
{arrow && (
<FloatingArrow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ export const SliderThumb = ({
<TooltipBase
appearance="neutral"
getRootRef={refs.setFloating}
style={convertFloatingDataToReactCSSProperties(
floatingPositionStrategy,
floatingDataX,
floatingDataY,
)}
style={convertFloatingDataToReactCSSProperties({
strategy: floatingPositionStrategy,
x: floatingDataX,
y: floatingDataY,
})}
arrowProps={{
coords: arrowCoords,
placement: resolvedPlacement,
Expand Down
3 changes: 3 additions & 0 deletions packages/vkui/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type AllowedFloatingComponentProps = Pick<
| 'defaultShown'
| 'onShownChange'
| 'hideWhenReferenceHidden'
| 'getFloatingElementHiddenStyles'
| 'children'
| 'zIndex'
| 'usePortal'
Expand Down Expand Up @@ -99,6 +100,7 @@ export const Tooltip = ({
hideWhenReferenceHidden,
disableFlipMiddleware = false,
disableTriggerOnFocus = false,
getFloatingElementHiddenStyles,

// useFloatingWithInteractions
defaultShown,
Expand Down Expand Up @@ -168,6 +170,7 @@ export const Tooltip = ({
disableInteractive: !enableInteractive,
middlewares,
strategy,
getFloatingElementHiddenStyles,
});
const tooltipRef = useExternRef<HTMLDivElement>(getRootRef, refs.setFloating);

Expand Down
71 changes: 67 additions & 4 deletions packages/vkui/src/lib/floating/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
checkIsNotAutoPlacement,
type ConvertFloatingDataArgs,
convertFloatingDataToReactCSSProperties,
getAutoPlacementAlign,
} from './functions';
Expand Down Expand Up @@ -42,11 +43,13 @@ describe('floating/functions', () => {
left: 10,
width: 'max-content',
};
expect(convertFloatingDataToReactCSSProperties('absolute', 10, 10)).toEqual({
expect(
convertFloatingDataToReactCSSProperties({ strategy: 'absolute', x: 10, y: 10 }),
).toEqual({
position: 'absolute',
...expectedCSSProperties,
});
expect(convertFloatingDataToReactCSSProperties('fixed', 10, 10)).toEqual({
expect(convertFloatingDataToReactCSSProperties({ strategy: 'fixed', x: 10, y: 10 })).toEqual({
position: 'fixed',
...expectedCSSProperties,
});
Expand All @@ -61,19 +64,79 @@ describe('floating/functions', () => {
left: 0,
width: 'max-content',
};
expect(convertFloatingDataToReactCSSProperties('absolute', 0, 0)).toEqual(
expect(convertFloatingDataToReactCSSProperties({ strategy: 'absolute', x: 0, y: 0 })).toEqual(
expectedCSSProperties,
);
});

it('should ignore `width` property if `initialWidth` prop is null', () => {
expect(convertFloatingDataToReactCSSProperties('absolute', 0, 0, null)).toEqual({
expect(
convertFloatingDataToReactCSSProperties({
strategy: 'absolute',
x: 0,
y: 0,
initialWidth: null,
}),
).toEqual({
position: 'absolute',
top: 0,
right: 'auto',
bottom: 'auto',
left: 0,
});
});

it('should use custom css properties with getFloatingElementHiddenStyles', () => {
const getFloatingElementHiddenStyles = (hidden: boolean) => {
if (hidden) {
return {
display: 'none',
};
}
return {
display: 'flex',
};
};
const args: ConvertFloatingDataArgs = {
strategy: 'absolute',
x: 0,
y: 0,
initialWidth: null,
getFloatingElementHiddenStyles,
};
const expectedStyles = {
position: 'absolute',
top: 0,
right: 'auto',
bottom: 'auto',
left: 0,
};
expect(
convertFloatingDataToReactCSSProperties({
...args,
middlewareData: {
hide: {
referenceHidden: true,
},
},
}),
).toEqual({
...expectedStyles,
display: 'none',
});
expect(
convertFloatingDataToReactCSSProperties({
...args,
middlewareData: {
hide: {
referenceHidden: false,
},
},
}),
).toEqual({
...expectedStyles,
display: 'flex',
});
});
});
});
41 changes: 32 additions & 9 deletions packages/vkui/src/lib/floating/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
PlacementWithAuto,
UseFloatingData,
} from './types/common';
import { type FloatingComponentProps } from './types/component';

export function checkIsNotAutoPlacement(placement: PlacementWithAuto): placement is Placement {
return !placement.startsWith('auto');
Expand All @@ -16,18 +17,39 @@
return align === 'start' || align === 'end' ? align : null;
}

const defaultGetFloatingElementHiddenStyles: Exclude<
FloatingComponentProps['getFloatingElementHiddenStyles'],
undefined
> = (hidden: boolean) => {
return hidden
? {
visibility: 'hidden',
}
: {};

Check warning on line 28 in packages/vkui/src/lib/floating/functions.ts

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/lib/floating/functions.ts#L28

Added line #L28 was not covered by tests
};

export type ConvertFloatingDataArgs = {
strategy: FloatingPositionStrategy;
x: UseFloatingData['x'];
y: UseFloatingData['y'];
initialWidth?: React.CSSProperties['width'] | null;
middlewareData?: UseFloatingData['middlewareData'];
getFloatingElementHiddenStyles?: FloatingComponentProps['getFloatingElementHiddenStyles'];
};

/**
* Note: не используем `translate3d`, чтобы в лишний раз не выносить в отдельный слой и не занимать память в GPU.
*
* см. https://floating-ui.com/docs/react#positioning
*/
export function convertFloatingDataToReactCSSProperties(
strategy: FloatingPositionStrategy,
x: UseFloatingData['x'],
y: UseFloatingData['y'],
initialWidth: React.CSSProperties['width'] | null = 'max-content',
middlewareData?: UseFloatingData['middlewareData'],
): React.CSSProperties {
export function convertFloatingDataToReactCSSProperties({
strategy,
x,
y,
initialWidth = 'max-content',
middlewareData,
getFloatingElementHiddenStyles = defaultGetFloatingElementHiddenStyles,
}: ConvertFloatingDataArgs): React.CSSProperties {
const styles: React.CSSProperties = {
position: strategy,
top: y,
Expand All @@ -40,8 +62,9 @@
}
if (middlewareData) {
const hide = middlewareData.hide;
if (hide && hide.referenceHidden) {
styles['visibility'] = 'hidden';
if (hide) {
const hiddenStyles = getFloatingElementHiddenStyles(hide.referenceHidden || false);
hiddenStyles && Object.assign(styles, hiddenStyles);
}
}
return styles;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type CSSProperties } from 'react';
import {
type FloatingPositionStrategy,
type Placement,
Expand Down Expand Up @@ -96,6 +97,11 @@ export interface UseFloatingWithInteractionsProps {
* Вызывается при каждом изменении видимости всплывающего элемента, но после завершении анимации.
*/
onShownChanged?: OnShownChange;
/**
* Колбэк для установки стилей при скрытии всплывающего элемента.
* Работает только совместно со свойством `hideWhenReferenceHidden`
*/
getFloatingElementHiddenStyles?: (hidden: boolean) => CSSProperties | undefined;
}

export type ReferenceProps<T = HTMLElement> = Omit<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const useFloatingWithInteractions = <T extends HTMLElement = HTMLElement>
middlewares,
hoverDelay = 0,
closeAfterClick = false,
getFloatingElementHiddenStyles,

// disables
disabled = false,
Expand Down Expand Up @@ -329,13 +330,13 @@ export const useFloatingWithInteractions = <T extends HTMLElement = HTMLElement>
}, [triggerOnHover, triggerOnFocus, triggerOnClick]);

if (shownFinalState) {
floatingPropsRef.current.style = convertFloatingDataToReactCSSProperties(
floatingPropsRef.current.style = convertFloatingDataToReactCSSProperties({
strategy,
x,
y,
undefined,
middlewareData,
);
getFloatingElementHiddenStyles,
});

if (disableInteractive) {
floatingPropsRef.current.style.pointerEvents = 'none';
Expand Down
Loading