Skip to content

Commit

Permalink
fix(popover): migrated to floating-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
hextion committed Oct 14, 2024
1 parent f567634 commit 0cfcf37
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 152 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@alfalab/utils": "^1.15.2",
"@dnd-kit/core": "^6.0.7",
"@dnd-kit/sortable": "^7.0.2",
"@floating-ui/react-dom": "^2.1.2",
"@juggle/resize-observer": "^3.3.1",
"@maskito/core": "^1.7.0",
"@maskito/react": "^1.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,7 @@ exports[`Snapshots tests should display opened correctly 2`] = `
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="bottom-start"
data-popper-reference-hidden="true"
style="z-index: 50; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
Expand Down Expand Up @@ -621,9 +619,7 @@ exports[`Snapshots tests should display opened correctly 4`] = `
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="bottom-start"
data-popper-reference-hidden="true"
style="z-index: 50; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
Expand Down Expand Up @@ -1024,9 +1020,7 @@ exports[`Snapshots tests should display opened correctly 6`] = `
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="bottom-start"
data-popper-reference-hidden="true"
style="z-index: 50; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
Expand Down
2 changes: 1 addition & 1 deletion packages/popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@alfalab/core-components-portal": "^3.3.3",
"@alfalab/core-components-stack": "^5.0.0",
"@alfalab/hooks": "^1.13.0",
"@floating-ui/react-dom": "^2.1.2",
"@juggle/resize-observer": "^3.3.1",
"@popperjs/core": "^2.11.8",
"classnames": "^2.3.1",
"popper-max-size-modifier": "^0.2.0",
"react-merge-refs": "^1.1.0",
Expand Down
170 changes: 47 additions & 123 deletions packages/popover/src/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,32 @@ import React, {
forwardRef,
MutableRefObject,
ReactNode,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import mergeRefs from 'react-merge-refs';
import { usePopper } from 'react-popper';
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';
import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observer';
import { BasePlacement, ModifierArguments, Obj, VariationPlacement } from '@popperjs/core';
import {
arrow,
autoUpdate,
flip,
offset as offsetMiddleware,
Placement,
useFloating,
} from '@floating-ui/react-dom';
import cn from 'classnames';
import maxSize from 'popper-max-size-modifier';

import { Portal } from '@alfalab/core-components-portal';
import { fnUtils, isClient } from '@alfalab/core-components-shared';
import { Stack } from '@alfalab/core-components-stack';
import { stackingOrder } from '@alfalab/stack-context';

import styles from './index.module.css';

type RefElement = HTMLElement | null;

export type Position = BasePlacement | VariationPlacement;

type PopperModifier = {
name: string;
options: Obj;
};
export type Position = Placement;

export type PopoverProps = {
/**
Expand Down Expand Up @@ -66,7 +63,8 @@ export type PopoverProps = {
preventOverflow?: boolean;

/**
* Позволяет поповеру подствраивать свою высоту под границы экрана, если из-за величины контента он выходит за рамки видимой области экрана
* Позволяет поповеру подствраивать свою высоту под границы экрана, если из-за величины контента он выходит за рамки видимой области экрана
* @deprecated Отсутствует необходимость использования
*/
availableHeight?: boolean;

Expand Down Expand Up @@ -119,7 +117,7 @@ export type PopoverProps = {

/**
* Хранит функцию, с помощью которой можно обновить положение компонента
* @deprecated
* @deprecated Отсутствует необходимость использования
*/
update?: MutableRefObject<(() => void) | undefined>;

Expand Down Expand Up @@ -157,39 +155,12 @@ const CSS_TRANSITION_CLASS_NAMES = {
exitActive: styles.exitActive,
};

const availableHieghtModifier = {
name: 'availableHeight',
enabled: true,
phase: 'beforeWrite',
requires: ['maxSize'],
fn({
state: {
modifiersData,
elements: { popper },
},
}: ModifierArguments<Obj>) {
const { height } = modifiersData.maxSize;

const content = popper.querySelector(`.${styles.scrollableContent}`) as HTMLElement;

if (content && !content.style.maxHeight) {
content.style.maxHeight = `${height}px`;
}
},
};

const DEFAULT_OFFSET = [0, 0];
const DEFAULT_OFFSET: [number, number] = [0, 0];

// Минимальное расстояние стрелки до края поповера
const MIN_DISTANCE_TO_EDGE = 24;

function getArrowPadding({
placement,
}: {
popper: { height: number; width: number };
reference: { height: number; width: number };
placement: Position;
}) {
function getArrowPadding(placement: Position) {
if (placement === 'right-end' || placement === 'left-end') {
return { top: MIN_DISTANCE_TO_EDGE, right: 0, bottom: 0, left: 0 };
}
Expand All @@ -216,7 +187,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
getPortalContainer,
transition = DEFAULT_TRANSITION,
anchorElement: referenceElement,
useAnchorWidth,
useAnchorWidth = false,
offset = DEFAULT_OFFSET,
withArrow = false,
withTransition = true,
Expand All @@ -235,103 +206,53 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
},
ref,
) => {
const [popperElement, setPopperElement] = useState<RefElement>(null);
const [arrowElement, setArrowElement] = useState<RefElement>(null);
const resizeObserverRef = useRef<ResizeObserver | null>(null);

const updatePopperRef = useRef<(() => void) | null>(null);

const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
const popperRef = useRef<HTMLDivElement>(null);

const popperModifiers = useMemo(() => {
const modifiers: PopperModifier[] = [{ name: 'offset', options: { offset } }];
const floatingMiddlewares = useMemo(() => {
const [crossAxis, mainAxis] = offset;
const middlewares = [offsetMiddleware({ mainAxis, crossAxis })];

if (withArrow) {
modifiers.push({
name: 'arrow',
options: {
element: arrowElement,
padding: getArrowPadding,
},
});
middlewares.push(
arrow(
(state) => ({
element: arrowElement,
padding: getArrowPadding(state.placement),
}),
[arrowElement],
),
);
}

if (preventFlip) {
modifiers.push({ name: 'flip', options: { fallbackPlacements: [] } });
if (preventFlip || fallbackPlacements || preventOverflow) {
middlewares.push(
flip({ flipAlignment: preventFlip, fallbackPlacements, mainAxis: false }),
);
}

if (fallbackPlacements) {
modifiers.push({ name: 'flip', options: { fallbackPlacements } });
}

if (preventOverflow) {
modifiers.push({ name: 'preventOverflow', options: { mainAxis: false } });
}
return middlewares;
}, [offset, withArrow, preventFlip, fallbackPlacements, preventOverflow, arrowElement]);

if (availableHeight) {
modifiers.push({ ...maxSize, options: {} });
modifiers.push({ ...availableHieghtModifier, options: {} });
}

return modifiers;
}, [
offset,
withArrow,
preventFlip,
fallbackPlacements,
preventOverflow,
availableHeight,
arrowElement,
]);

const {
styles: popperStyles,
attributes,
update: updatePopper,
} = usePopper(referenceElement, popperElement, {
const { floatingStyles, refs, middlewareData, placement } = useFloating<HTMLElement>({
placement: position,
modifiers: popperModifiers,
elements: { reference: referenceElement },
whileElementsMounted: autoUpdate,
middleware: floatingMiddlewares,
});

if (!(updatePopperRef.current === updatePopper)) {
updatePopperRef.current = updatePopper;
}

if (isClient() && fnUtils.isNil(resizeObserverRef.current)) {
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
const updatePopperCallback = () => {
updatePopperRef.current?.();
};

resizeObserverRef.current = new ResizeObserver(updatePopperCallback);
}

useEffect(() => {
if (fnUtils.isNil(popperElement)) return fnUtils.noop;
resizeObserverRef.current?.observe(popperElement);

return () => resizeObserverRef.current?.unobserve(popperElement);
}, [popperElement]);

useEffect(() => {
if (!useAnchorWidth || fnUtils.isNil(referenceElement)) return fnUtils.noop;
resizeObserverRef.current?.observe(referenceElement);

return () => resizeObserverRef.current?.unobserve(referenceElement);
}, [referenceElement, useAnchorWidth]);

const renderContent = (computedZIndex: number) => (
<div
ref={mergeRefs([ref, popperRef, setPopperElement])}
ref={mergeRefs([ref, popperRef, refs.setFloating])}
style={{
zIndex: computedZIndex,
width: useAnchorWidth ? referenceElement?.offsetWidth : undefined,
...popperStyles.popper,
...(popperStyles.popper?.transform ? null : { visibility: 'hidden' }),
...floatingStyles,
...(floatingStyles.transform ? null : { visibility: 'hidden' }),
}}
data-test-id={dataTestId}
className={cn(styles.component, className)}
{...attributes.popper}
data-popper-placement={placement}
>
<div
className={cn(styles.inner, popperClassName)}
Expand All @@ -344,11 +265,14 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
<div className={cn({ [styles.scrollableContent]: availableHeight })}>
{children}
</div>

{withArrow && (
<div
ref={setArrowElement}
style={popperStyles.arrow}
style={{
transform: `translate(${middlewareData.arrow?.x ?? 0}px, ${
middlewareData.arrow?.y ?? 0
}px)`,
}}
className={cn(styles.arrow, arrowClassName)}
/>
)}
Expand Down
8 changes: 2 additions & 6 deletions packages/popover/src/__snapshots__/Component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ Object {
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="left"
data-popper-reference-hidden="true"
style="z-index: 50; position: absolute; left: 0px; top: 0px; right: 0px; transform: translate(0px, 0px);"
style="z-index: 50; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
class="inner"
Expand Down Expand Up @@ -93,10 +91,8 @@ Object {
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="left"
data-popper-reference-hidden="true"
style="z-index: 50; position: absolute; left: 0px; top: 0px; right: 0px; transform: translate(0px, 0px);"
style="z-index: 50; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
class="inner"
Expand Down
5 changes: 3 additions & 2 deletions packages/popover/src/docs/Component.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Meta, Story, Markdown } from '@storybook/addon-docs';
import { boolean, select, number } from '@storybook/addon-knobs';
import { boolean, select, number, text } from '@storybook/addon-knobs';
import { ComponentHeader, Tabs } from 'storybook/blocks';

import { Button } from '@alfalab/core-components-button';
Expand Down Expand Up @@ -44,6 +44,7 @@ import {
const transitionTimeout = number('transition.timeout (ms)', 150);
const previewStyles = stylesStringToObj(getQueryParam('wrapperStyles'));
const isPreview = Object.keys(previewStyles).length > 0;
const buttonLabel = text('buttonLabel', 'Popover');
return (
<div
style={
Expand Down Expand Up @@ -84,7 +85,7 @@ import {
<div ref={handleRef} />
) : (
<Button ref={handleRef} onClick={toggle}>
{open ? 'Скрыть' : 'Показать'} Popover
{open ? 'Скрыть' : 'Показать'} {buttonLabel}
</Button>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/popover/src/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

.arrow {
z-index: 1;
position: absolute;
bottom: 100%;
}

.arrow:after {
Expand Down
8 changes: 2 additions & 6 deletions packages/toast/src/__snapshots__/component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ exports[`Toast Snapshots tests should math snapshot when prop \`anchorElement\`
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="left"
data-popper-reference-hidden="true"
style="z-index: 1000; width: 0px; position: absolute; left: 0px; top: 0px; right: 0px; transform: translate(0px, 0px);"
style="z-index: 1000; width: 0px; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
class="inner popoverInner"
Expand Down Expand Up @@ -113,10 +111,8 @@ exports[`Toast Snapshots tests should math snapshot when prop \`anchorElement\`
>
<div
class="component"
data-popper-escaped="true"
data-popper-placement="left"
data-popper-reference-hidden="true"
style="z-index: 1000; position: absolute; left: 0px; top: 0px; right: 0px; transform: translate(0px, 0px);"
style="z-index: 1000; position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
>
<div
class="inner popoverInner"
Expand Down
Loading

0 comments on commit 0cfcf37

Please sign in to comment.