Skip to content

Commit

Permalink
feat(tooltip): add TooltipResponsive, add fallbackPlacements prop (#…
Browse files Browse the repository at this point in the history
…780)

* feat(popover): add `fallbackPlacements` prop

* feat(tooltip): add TooltipResponsive,  add `fallbackPlacements` prop

* feat(popover): add forwardRef

* feat(popover): add  prop

* docs(tooltip): update docs
  • Loading branch information
dmitrsavk committed Aug 23, 2021
1 parent f1950d6 commit 61d780c
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 126 deletions.
3 changes: 2 additions & 1 deletion packages/popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@alfalab/core-components-stack": "^3.0.1",
"@popperjs/core": "^2.3.3",
"classnames": "^2.2.6",
"react-popper": "^2.2.2",
"react-merge-refs": "^1.1.0",
"react-popper": "^2.2.5",
"react-transition-group": "^4.3.0"
},
"devDependencies": {
Expand Down
256 changes: 149 additions & 107 deletions packages/popover/src/Component.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React, { useState, useEffect, useCallback, CSSProperties, MutableRefObject } from 'react';
import React, {
useState,
useEffect,
useCallback,
CSSProperties,
MutableRefObject,
forwardRef,
ReactNode,
} from 'react';
import cn from 'classnames';
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';
import { usePopper } from 'react-popper';
import { BasePlacement, VariationPlacement, Obj } from '@popperjs/core';
import mergeRefs from 'react-merge-refs';

import { Stack, stackingOrder } from '@alfalab/core-components-stack';
import { Portal } from '@alfalab/core-components-portal';
Expand Down Expand Up @@ -41,6 +50,11 @@ export type PopoverProps = {
*/
preventFlip?: boolean;

/**
* Запрещает поповеру менять свою позицию, если он не влезает в видимую область.
*/
preventOverflow?: boolean;

/**
* Если `true`, будет отрисована стрелочка
*/
Expand Down Expand Up @@ -102,6 +116,18 @@ export type PopoverProps = {
* z-index компонента
*/
zIndex?: number;

/**
* Если поповер не помещается в переданной позиции (position), он попробует открыться в другой позиции,
* по очереди для каждой позиции из этого списка.
* Если не передавать, то поповер открывается в противоположном направлении от переданного position.
*/
fallbackPlacements?: Position[];

/**
* Контент
*/
children?: ReactNode;
};

const DEFAULT_TRANSITION = {
Expand All @@ -115,113 +141,129 @@ const CSS_TRANSITION_CLASS_NAMES = {
exitActive: styles.exitActive,
};

export const Popover: React.FC<PopoverProps> = ({
children,
getPortalContainer,
transition = DEFAULT_TRANSITION,
anchorElement,
offset = [0, 0],
withArrow = false,
withTransition = true,
position = 'left',
preventFlip,
popperClassName,
arrowClassName,
className,
open,
dataTestId,
update,
transitionDuration = `${transition.timeout}ms`,
zIndex = stackingOrder.POPOVER,
}) => {
const [referenceElement, setReferenceElement] = useState<RefElement>(anchorElement);
const [popperElement, setPopperElement] = useState<RefElement>(null);
const [arrowElement, setArrowElement] = useState<RefElement>(null);

const getModifiers = useCallback(() => {
const modifiers: PopperModifier[] = [{ name: 'offset', options: { offset } }];

if (withArrow) {
modifiers.push({ name: 'arrow', options: { element: arrowElement } });
}

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

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

const { styles: popperStyles, attributes, update: updatePopper } = usePopper(
referenceElement,
popperElement,
export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
(
{
placement: position,
modifiers: getModifiers(),
children,
getPortalContainer,
transition = DEFAULT_TRANSITION,
anchorElement,
offset = [0, 0],
withArrow = false,
withTransition = true,
position = 'left',
preventFlip,
popperClassName,
arrowClassName,
className,
open,
dataTestId,
update,
transitionDuration = `${transition.timeout}ms`,
zIndex = stackingOrder.POPOVER,
fallbackPlacements,
preventOverflow = true,
},
);

useEffect(() => {
setReferenceElement(anchorElement);
}, [anchorElement]);

useEffect(() => {
if (updatePopper) {
updatePopper();
}
}, [updatePopper, arrowElement, children]);

useEffect(() => {
if (update && updatePopper) {
// eslint-disable-next-line no-param-reassign
update.current = updatePopper;
}
}, [updatePopper, update]);

const renderContent = (computedZIndex: number, style?: CSSProperties) => {
return (
<div
ref={setPopperElement}
style={{
zIndex: computedZIndex,
...popperStyles.popper,
}}
data-test-id={dataTestId}
className={cn(styles.component, className)}
{...attributes.popper}
>
<div className={cn(styles.inner, popperClassName)} style={style}>
{children}
{withArrow && (
<div
ref={setArrowElement}
style={popperStyles.arrow}
className={cn(styles.arrow, arrowClassName)}
/>
)}
ref,
) => {
const [referenceElement, setReferenceElement] = useState<RefElement>(anchorElement);
const [popperElement, setPopperElement] = useState<RefElement>(null);
const [arrowElement, setArrowElement] = useState<RefElement>(null);

const getModifiers = useCallback(() => {
const modifiers: PopperModifier[] = [{ name: 'offset', options: { offset } }];

if (withArrow) {
modifiers.push({ name: 'arrow', options: { element: arrowElement } });
}

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

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

if (preventOverflow) {
modifiers.push({ name: 'preventOverflow', options: { mainAxis: false } });
}

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

const { styles: popperStyles, attributes, update: updatePopper } = usePopper(
referenceElement,
popperElement,
{
placement: position,
modifiers: getModifiers(),
},
);

useEffect(() => {
setReferenceElement(anchorElement);
}, [anchorElement]);

useEffect(() => {
if (updatePopper) {
updatePopper();
}
}, [updatePopper, arrowElement, children]);

useEffect(() => {
if (update && !update.current && updatePopper) {
// eslint-disable-next-line no-param-reassign
update.current = updatePopper;
}
});

const renderContent = (computedZIndex: number, style?: CSSProperties) => {
return (
<div
ref={mergeRefs([ref, setPopperElement])}
// ref={setPopperElement}
style={{
zIndex: computedZIndex,
...popperStyles.popper,
}}
data-test-id={dataTestId}
className={cn(styles.component, className)}
{...attributes.popper}
>
<div className={cn(styles.inner, popperClassName)} style={style}>
{children}
{withArrow && (
<div
ref={setArrowElement}
style={popperStyles.arrow}
className={cn(styles.arrow, arrowClassName)}
/>
)}
</div>
</div>
</div>
);
};

return (
<Stack value={zIndex}>
{computedZIndex => (
<Portal getPortalContainer={getPortalContainer}>
{withTransition ? (
<CSSTransition
unmountOnExit={true}
classNames={CSS_TRANSITION_CLASS_NAMES}
{...transition}
in={open}
>
{renderContent(computedZIndex, { transitionDuration })}
</CSSTransition>
) : (
open && renderContent(computedZIndex)
)}
</Portal>
)}
</Stack>
);
};

return (
<Stack value={zIndex}>
{computedZIndex => (
<Portal getPortalContainer={getPortalContainer}>
{withTransition ? (
<CSSTransition
unmountOnExit={true}
classNames={CSS_TRANSITION_CLASS_NAMES}
{...transition}
in={open}
>
{renderContent(computedZIndex, { transitionDuration })}
</CSSTransition>
) : (
open && renderContent(computedZIndex)
)}
</Portal>
)}
</Stack>
);
};
},
);
2 changes: 1 addition & 1 deletion packages/popover/src/docs/description.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ render(() => {
alignItems: 'center',
}}
>
<Popover anchorElement={buttonElement} open={open}>
<Popover anchorElement={buttonElement} open={open} preventFlip={true}>
<div style={{ padding: '15px', width: '156px' }}>I am popover</div>
</Popover>

Expand Down
9 changes: 7 additions & 2 deletions packages/select/src/Component.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { ForwardRefRenderFunction } from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

Expand All @@ -10,6 +10,10 @@ import * as fieldModule from './components/field';
import * as optionsListModule from './components/options-list';
import * as optionModule from './components/option';

type PopoverComponent = {
render?: ForwardRefRenderFunction<HTMLDivElement, popoverModule.PopoverProps>;
};

const baseProps = {
options: [],
};
Expand Down Expand Up @@ -488,7 +492,8 @@ describe('Select', () => {
});

it('should transfer props to Popover', () => {
const spy = jest.spyOn(popoverModule, 'Popover');
const PopoverComponent = popoverModule.Popover as PopoverComponent;
const spy = jest.spyOn(PopoverComponent, 'render');

const cb = () => undefined;

Expand Down
10 changes: 8 additions & 2 deletions packages/toast/src/component.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React, { forwardRef } from 'react';
import React, { forwardRef, ForwardRefRenderFunction } from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as popoverModule from '@alfalab/core-components-popover';
import { Toast, ToastProps } from './index';

type PopoverComponent = {
render?: ForwardRefRenderFunction<HTMLDivElement, popoverModule.PopoverProps>;
};

describe('Toast', () => {
jest.useFakeTimers();

Expand Down Expand Up @@ -65,7 +69,9 @@ describe('Toast', () => {
});

it('should pass props to Popover', () => {
const popoverComponentSpy = jest.spyOn(popoverModule, 'Popover');
const PopoverComponent = popoverModule.Popover as PopoverComponent;

const popoverComponentSpy = jest.spyOn(PopoverComponent, 'render');

const anchorElement = document.createElement('div');
document.body.appendChild(anchorElement);
Expand Down
2 changes: 2 additions & 0 deletions packages/tooltip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"react-dom": "^16.9.0 || ^17.0.1"
},
"dependencies": {
"@alfalab/core-components-bottom-sheet": "^2.1.0",
"@alfalab/core-components-popover": "^5.2.0",
"@alfalab/hooks": "^1.3.0",
"classnames": "^2.2.6",
"react-merge-refs": "^1.1.0"
}
Expand Down
9 changes: 9 additions & 0 deletions packages/tooltip/src/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ export type TooltipProps = {
* Реф для обертки над дочерними элементами
*/
targetRef?: MutableRefObject<HTMLElement | null>;

/**
* Если тултип не помещается в переданной позиции (position), он попробует открыться в другой позиции,
* по очереди для каждой позиции из этого списка.
* Если не передавать, то тултип открывается в противоположном направлении от переданного position.
*/
fallbackPlacements?: PopoverProps['fallbackPlacements'];
};

export const Tooltip: FC<TooltipProps> = ({
Expand All @@ -145,6 +152,7 @@ export const Tooltip: FC<TooltipProps> = ({
getPortalContainer,
view = 'tooltip',
targetRef = null,
fallbackPlacements,
}) => {
const [visible, setVisible] = useState(!!forcedOpen);
const [target, setTarget] = useState<HTMLElement | null>(null);
Expand Down Expand Up @@ -314,6 +322,7 @@ export const Tooltip: FC<TooltipProps> = ({
position={position}
update={updatePopover}
zIndex={zIndex}
fallbackPlacements={fallbackPlacements}
>
<div {...getContentProps()}>{content}</div>
</Popover>
Expand Down

0 comments on commit 61d780c

Please sign in to comment.