Skip to content

Commit

Permalink
feat: Tooltip with react-popper (#267)
Browse files Browse the repository at this point in the history
* feat: init use react-popper on Tooltip component

* refactor: remove global styles and use popper attributes for placement

* fix: access placement map direcly and add comments

* fix: fixies and function for deprecated values

* docs: update documentation for tooltip

* fix: remove early return and use content to display or not the TooltipBody

* fix: update tooltip docs to show react popper instead of thetter

* fix: use popper placement instead of TooltipPlacement on TooltipPlacementExample

* fix: move map function to TooltipPlacement.ts back

* fix: left and right start and end arrow placements

* fix: wrap on PropTable column Type on whitespace

* fix: remove done todos

Co-authored-by: Nelson Dornelas <nelson.dornelas@free-now.com>
Co-authored-by: Leonardo Di Vittorio <leonardo.divittorio@Leonardos-MacBook-Pro.local>
  • Loading branch information
3 people committed Aug 19, 2022
1 parent 15ce089 commit 6965ef4
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 186 deletions.
228 changes: 119 additions & 109 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { PropsWithChildren } from 'react';
import * as React from 'react';
import TetherComponent from 'react-tether';
import styled, { createGlobalStyle, keyframes } from 'styled-components';
import { Colors, Elevation, MediaQueries } from '../../essentials';
import styled, { keyframes } from 'styled-components';
import { usePopper } from 'react-popper';
import { Placement } from '@popperjs/core/lib/enums';
import { variant } from 'styled-system';
import { Colors, MediaQueries } from '../../essentials';
import { get } from '../../utils/themeGet';
import { Text } from '../Text/Text';
import { TooltipPlacement } from './TooltipPlacement';
import { getAttachmentFromPlacement } from './util/getAttachmentFromPlacement';
import { mapPlacementWithDeprecationWarning, TooltipPlacement } from './TooltipPlacement';

const fadeAnimation = keyframes`
from {
Expand All @@ -18,7 +19,73 @@ const fadeAnimation = keyframes`
}
`;

const TooltipBody = styled.div<Pick<TooltipProps, 'inverted'>>`
const arrowPlacementStyles = variant({
variants: {
bottom: {
right: 'calc(50% - 0.25rem)'
},
'bottom-end': {
right: '0.3rem'
},
'top-start': {
bottom: '-0.5rem',
transform: 'rotate(-180deg)'
},
top: {
bottom: '-0.5rem',
transform: 'rotate(-180deg)',
right: 'calc(50% - 0.25rem)'
},
'top-end': {
bottom: '-0.5rem',
transform: 'rotate(-180deg)',
right: '0.3rem'
},
left: {
top: 'calc(50% - 0.25rem)',
left: 'auto',
right: '-0.5rem',
transform: 'rotate(90deg)'
},
'left-end': {
bottom: '0.5rem',
left: 'auto',
right: '-0.5rem',
transform: 'rotate(90deg)'
},
'left-start': {
top: '0.5rem',
left: 'auto',
right: '-0.5rem',
transform: 'rotate(90deg)'
},
right: {
top: 'calc(50% - 0.25rem)',
left: '-0.25rem',
right: 'auto',
transform: 'rotate(-90deg)'
},
'right-end': {
bottom: '0.5rem',
left: '-0.25rem',
right: 'auto',
transform: 'rotate(-90deg)'
},
'right-start': {
top: '0.5rem',
left: '-0.25rem',
right: 'auto',
transform: 'rotate(-90deg)'
}
}
});

interface TooltipBodyProps {
inverted?: boolean;
variant: string;
}

const TooltipBody = styled.div<TooltipBodyProps>`
position: relative;
background-color: ${p => (p.inverted ? Colors.AUTHENTIC_BLUE_50 : Colors.AUTHENTIC_BLUE_900)};
padding: 0.25rem 0.5rem;
Expand All @@ -45,83 +112,8 @@ const TooltipBody = styled.div<Pick<TooltipProps, 'inverted'>>`
border: 0.25rem solid rgba(0, 0, 0, 0);
border-bottom-color: ${p => (p.inverted ? Colors.AUTHENTIC_BLUE_50 : Colors.AUTHENTIC_BLUE_900)};
margin-left: -0.25rem;
}
`;

const GlobalTetherStyles = createGlobalStyle`
body > .tether-element {
z-index: ${Elevation.TOOLTIP};
}
.tether-target-attached-bottom {
& > ${TooltipBody} {
margin-top: 0.5rem;
&::after {
top: -0.5rem;
}
}
}
.tether-target-attached-top {
& > ${TooltipBody} {
top: -0.5rem;
&::after {
bottom: -0.5rem;
transform: rotate(-180deg);
}
}
}
.tether-target-attached-center {
& > ${TooltipBody} {
&::after {
left: 50%;
}
}
}
.tether-target-attached-left {
& > ${TooltipBody} {
&::after {
left: 1rem;
}
}
}
.tether-target-attached-right {
& > ${TooltipBody} {
&::after {
right: 1rem;
}
}
}
.tether-target-attached-middle.tether-target-attached-right {
& > ${TooltipBody} {
margin-left: 0.5rem;
&::after {
top: calc(50% - 0.25rem);
left: -0.25rem;
right: auto;
transform: rotate(-90deg);
}
}
}
.tether-target-attached-middle.tether-target-attached-left {
& > ${TooltipBody} {
left: -0.5rem;
&::after {
top: calc(50% - 0.25rem);
left: auto;
right: -0.5rem;
transform: rotate(90deg);
}
}
${arrowPlacementStyles}
}
`;

Expand All @@ -131,9 +123,9 @@ interface TooltipProps {
*/
content: React.ReactNode;
/**
* Set the position of where the tooltip is attached to the target, defaults to "top-center"
* Set the position of where the tooltip is attached to the target, defaults to "top"
*/
placement?: TooltipPlacement;
placement?: TooltipPlacement | Placement;
/**
* Adjust the component for display on dark backgrounds
*/
Expand All @@ -147,11 +139,37 @@ interface TooltipProps {
const Tooltip: React.FC<TooltipProps> = ({
content,
children,
placement = 'top-center',
placement = 'top',
alwaysVisible = false,
inverted = false
}: PropsWithChildren<TooltipProps>) => {
const [isVisible, setIsVisible] = React.useState(alwaysVisible);
/**
* triggerReference and contentReference are used with the Popper library in order to get the tooltip styles and attributes
*/
const [triggerReference, setTriggerReference] = React.useState(undefined);
const [contentReference, setContentReference] = React.useState(undefined);


/**
* Map the older placement values to Popper placement as we need to get the correct placement for the tooltip from the Popper library
* without introduce any breaking changes to the Tooltip component.
* TODO: Remove in the next major release.
*/
const mappedPlacement = mapPlacementWithDeprecationWarning(placement);

const { styles, attributes } = usePopper(triggerReference, contentReference, {
placement: mappedPlacement,
modifiers: [
{
name: 'offset',
enabled: true,
options: {
offset: [0, 5]
}
}
]
});

let dynamicContent = content;

Expand All @@ -171,30 +189,22 @@ const Tooltip: React.FC<TooltipProps> = ({

return (
<>
<TetherComponent
{...getAttachmentFromPlacement(placement)}
constraints={[
{
to: 'window',
attachment: 'together'
}
]}
renderTarget={ref =>
React.cloneElement(children as React.ReactElement, {
onMouseOver: () => handleVisibilityChange(true),
onMouseOut: () => handleVisibilityChange(false),
ref
})
}
renderElement={(ref: React.RefObject<HTMLDivElement>) =>
isVisible && (
<TooltipBody ref={ref} inverted={inverted}>
{dynamicContent}
</TooltipBody>
)
}
/>
<GlobalTetherStyles />
{React.cloneElement(children as React.ReactElement, {
onMouseOver: () => handleVisibilityChange(true),
onMouseOut: () => handleVisibilityChange(false),
ref: setTriggerReference
})}
{content && isVisible && (
<TooltipBody
ref={setContentReference}
inverted={inverted}
style={{ ...styles.popper }}
variant={attributes.popper?.['data-popper-placement']}
{...attributes.popper}
>
{dynamicContent}
</TooltipBody>
)}
</>
);
};
Expand Down
21 changes: 21 additions & 0 deletions src/components/Tooltip/TooltipPlacement.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Placement } from '@popperjs/core';
import { deprecatedProperty } from '../../utils/deprecatedProperty';

export type TooltipPlacement =
| 'bottom-left'
| 'bottom-center'
Expand All @@ -7,3 +10,21 @@ export type TooltipPlacement =
| 'top-right'
| 'center-left'
| 'center-right';

const TOOLTIP_TO_POPPER_PLACEMENT_MAP: { [key in TooltipPlacement]: Placement } = {
'bottom-left': 'bottom-start',
'bottom-center': 'bottom',
'bottom-right': 'bottom-end',
'top-left': 'top-start',
'top-center': 'top',
'top-right': 'top-end',
'center-left': 'left',
'center-right': 'right'
};

export const mapPlacementWithDeprecationWarning = (placement: TooltipPlacement | Placement): Placement => {
const mappedPlacement = TOOLTIP_TO_POPPER_PLACEMENT_MAP[placement as TooltipPlacement];
if (mappedPlacement)
deprecatedProperty('Tooltip', placement, `Value '${placement}' for placement`, mappedPlacement);
return mappedPlacement ?? (placement as Placement);
};
6 changes: 5 additions & 1 deletion src/components/Tooltip/docs/Tooltip.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ This component provides informative text to an UI element.

Use the tooltip for user on-boarding, guiding information about a new feature, detailed interactive workflows or for a contextual help (eg. over a button).

## Properties

<TooltipPropsTable />

## Appearance

- The font size is 12px and font weight is regular.
Expand All @@ -49,7 +53,7 @@ Use the tooltip for user on-boarding, guiding information about a new feature, d
It is possible to adjust the position of the tooltip connection to the target with the `placement` prop. Below is a list
of possible options which are represented in the square next to it. It is important to keep in mind that the tooltip
will be moved to a different position if it cannot be shown on the desired side due to screen sizes. Read more about the
Tether library [here](http://tether.io).
Popper library [here](https://popper.js.org/).

<TooltipPlacementExample />

Expand Down
29 changes: 18 additions & 11 deletions src/components/Tooltip/docs/TooltipPlacementExample.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FC } from 'react';
import * as React from 'react';
import styled from 'styled-components';
import { Placement } from '@popperjs/core/lib/enums';
import { RadioButton, Tooltip } from '../..';
import { Colors, MediaQueries } from '../../../essentials';
import { TooltipPlacement } from '../TooltipPlacement';

const TargetSquare = styled.div`
background: ${Colors.BUMPY_MAGENTA_50};
Expand Down Expand Up @@ -35,17 +35,24 @@ const ExampleContainer = styled.div`
`;

const TooltipPlacementExample: FC = () => {
const [placement, setPlacement] = React.useState<TooltipPlacement>('top-center');
const [placement, setPlacement] = React.useState<Placement>('top');

const availablePlacements: TooltipPlacement[] = [
'top-left',
'top-center',
'top-right',
'bottom-left',
'bottom-center',
'bottom-right',
'center-left',
'center-right'
const availablePlacements: Placement[] = [
'top-start',
'top-end',
'bottom-start',
'bottom-end',
'right-start',
'right-end',
'left-start',
'left-end',
'top',
'bottom',
'right',
'left',
'auto',
'auto-start',
'auto-end'
];

return (
Expand Down
Loading

0 comments on commit 6965ef4

Please sign in to comment.