Skip to content

Commit

Permalink
Revert Tooltip to class component (#3714)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->
Revert Tooltip component to Class to remove react throwing errors about
defaultProps - see linked issue

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->
#3615 

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
Tooltip is the most risky component to refactor to use default params
and it has broken functionality in the past

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->
- Run storybook and test tooltip as normal
- Test accessibility layer

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [c] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [] I have added tests to cover my changes.
- [x] All new and existing tests passed.

---------

Co-authored-by: Coltin Kifer <[email protected]>
  • Loading branch information
ckifer and Coltin Kifer authored Aug 25, 2023
1 parent d6aa41f commit f4a950b
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 172 deletions.
336 changes: 166 additions & 170 deletions src/component/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
/**
* @fileOverview Tooltip
*/
import React, {
CSSProperties,
ReactNode,
ReactElement,
SVGProps,
useEffect,
useState,
useRef,
useCallback,
} from 'react';
import React, { PureComponent, CSSProperties, ReactNode, ReactElement, SVGProps } from 'react';
import { translateStyle } from 'react-smooth';
import _ from 'lodash';
import classNames from 'classnames';
Expand Down Expand Up @@ -99,88 +90,99 @@ export type TooltipProps<TValue extends ValueType, TName extends NameType> = Def
useTranslate3d?: boolean;
};

const tooltipDefaultProps: TooltipProps<number, string> = {
active: false,
allowEscapeViewBox: { x: false, y: false },
reverseDirection: { x: false, y: false },
offset: 10,
viewBox: { x: 0, y: 0, height: 0, width: 0 },
coordinate: { x: 0, y: 0 },
// this doesn't exist on TooltipProps
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
cursorStyle: {},
separator: ' : ',
wrapperStyle: {},
contentStyle: {},
itemStyle: {},
labelStyle: {},
cursor: true,
trigger: 'hover',
isAnimationActive: !Global.isSsr,
animationEasing: 'ease',
animationDuration: 400,
filterNull: true,
useTranslate3d: false,
};
export class Tooltip<TValue extends ValueType, TName extends NameType> extends PureComponent<
TooltipProps<TValue, TName>
> {
static displayName = 'Tooltip';

export const Tooltip = <TValue extends ValueType, TName extends NameType>(
props: TooltipProps<TValue, TName> & { children?: React.ReactNode },
) => {
const [boxWidth, setBoxWidth] = useState(-1);
const [boxHeight, setBoxHeight] = useState(-1);
const [dismissed, setDismissed] = useState(false);
const [dismissedAtCoordinate, setDismissedAtCoordinate] = useState({ x: 0, y: 0 });

const wrapperNode = useRef<HTMLDivElement>();
const { allowEscapeViewBox, reverseDirection, coordinate, offset, position, viewBox } = props;

const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Escape') {
setDismissed(true);
setDismissedAtCoordinate(prev => ({
...prev,
x: coordinate?.x,
y: coordinate?.y,
}));
}
},
[coordinate?.x, coordinate?.y],
);

useEffect(() => {
const updateBBox = () => {
if (dismissed) {
document.removeEventListener('keydown', handleKeyDown);
if (coordinate?.x !== dismissedAtCoordinate.x || coordinate?.y !== dismissedAtCoordinate.y) {
setDismissed(false);
}
} else {
document.addEventListener('keydown', handleKeyDown);
}
static defaultProps = {
active: false,
allowEscapeViewBox: { x: false, y: false },
reverseDirection: { x: false, y: false },
offset: 10,
viewBox: { x: 0, y: 0, height: 0, width: 0 },
coordinate: { x: 0, y: 0 },
cursorStyle: {},
separator: ' : ',
wrapperStyle: {},
contentStyle: {},
itemStyle: {},
labelStyle: {},
cursor: true,
trigger: 'hover',
isAnimationActive: !Global.isSsr,
animationEasing: 'ease',
animationDuration: 400,
filterNull: true,
useTranslate3d: false,
};

if (wrapperNode.current && wrapperNode.current.getBoundingClientRect) {
const box = wrapperNode.current.getBoundingClientRect();
state = {
boxWidth: -1,
boxHeight: -1,
dismissed: false,
dismissedAtCoordinate: { x: 0, y: 0 },
};

private wrapperNode: HTMLDivElement;

componentDidMount() {
this.updateBBox();
}

componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}

if (Math.abs(box.width - boxWidth) > EPS || Math.abs(box.height - boxHeight) > EPS) {
setBoxWidth(box.width);
setBoxHeight(box.height);
}
} else if (boxWidth !== -1 || boxHeight !== -1) {
setBoxWidth(-1);
setBoxHeight(-1);
componentDidUpdate() {
this.updateBBox();
}

handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
this.setState({
dismissed: true,
dismissedAtCoordinate: {
...this.state.dismissedAtCoordinate,
x: this.props.coordinate.x,
y: this.props.coordinate.y,
},
});
}
};

updateBBox() {
const { boxWidth, boxHeight, dismissed } = this.state;
if (dismissed) {
document.removeEventListener('keydown', this.handleKeyDown);
if (
this.props.coordinate.x !== this.state.dismissedAtCoordinate.x ||
this.props.coordinate.y !== this.state.dismissedAtCoordinate.y
) {
this.setState({ dismissed: false });
}
};
} else {
document.addEventListener('keydown', this.handleKeyDown);
}

updateBBox();
if (this.wrapperNode && this.wrapperNode.getBoundingClientRect) {
const box = this.wrapperNode.getBoundingClientRect();

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [boxHeight, boxWidth, coordinate, dismissed, dismissedAtCoordinate.x, dismissedAtCoordinate.y, handleKeyDown]);
if (Math.abs(box.width - boxWidth) > EPS || Math.abs(box.height - boxHeight) > EPS) {
this.setState({
boxWidth: box.width,
boxHeight: box.height,
});
}
} else if (boxWidth !== -1 || boxHeight !== -1) {
this.setState({
boxWidth: -1,
boxHeight: -1,
});
}
}

const getTranslate = ({
getTranslate = ({
key,
tooltipDimension,
viewBoxDimension,
Expand All @@ -189,17 +191,19 @@ export const Tooltip = <TValue extends ValueType, TName extends NameType>(
tooltipDimension: number;
viewBoxDimension: number;
}) => {
const { allowEscapeViewBox, reverseDirection, coordinate, offset, position, viewBox } = this.props;

if (position && isNumber(position[key])) {
return position[key];
}

const negative = coordinate[key] - tooltipDimension - offset;
const positive = coordinate[key] + offset;
if (allowEscapeViewBox?.[key]) {
if (allowEscapeViewBox[key]) {
return reverseDirection[key] ? negative : positive;
}

if (reverseDirection?.[key]) {
if (reverseDirection[key]) {
const tooltipBoundary = negative;
const viewBoxBoundary = viewBox[key];
if (tooltipBoundary < viewBoxBoundary) {
Expand All @@ -215,97 +219,89 @@ export const Tooltip = <TValue extends ValueType, TName extends NameType>(
return Math.max(positive, viewBox[key]);
};

const {
payload,
payloadUniqBy,
filterNull,
active,
wrapperStyle,
useTranslate3d,
isAnimationActive,
animationDuration,
animationEasing,
} = props;
const finalPayload = getUniqPayload(
payloadUniqBy,
filterNull && payload && payload.length ? payload.filter(entry => !_.isNil(entry.value)) : payload,
);
const hasPayload = finalPayload && finalPayload.length;
const { content } = props;
let outerStyle: CSSProperties = {
pointerEvents: 'none',
visibility: !dismissed && active && hasPayload ? 'visible' : 'hidden',
position: 'absolute',
top: 0,
left: 0,
...wrapperStyle,
};
let translateX, translateY;

if (position && isNumber(position.x) && isNumber(position.y)) {
translateX = position.x;
translateY = position.y;
} else if (boxWidth > 0 && boxHeight > 0 && coordinate) {
translateX = getTranslate({
key: 'x',
tooltipDimension: boxWidth,
viewBoxDimension: viewBox.width,
});
render() {
const { payload, isAnimationActive, animationDuration, animationEasing, filterNull, payloadUniqBy } = this.props;
const finalPayload = getUniqPayload(
payloadUniqBy,
filterNull && payload && payload.length ? payload.filter(entry => !_.isNil(entry.value)) : payload,
);
const hasPayload = finalPayload && finalPayload.length;
const { content, viewBox, coordinate, position, active, wrapperStyle } = this.props;
let outerStyle: CSSProperties = {
pointerEvents: 'none',
visibility: !this.state.dismissed && active && hasPayload ? 'visible' : 'hidden',
position: 'absolute',
top: 0,
left: 0,
...wrapperStyle,
};
let translateX, translateY;

translateY = getTranslate({
key: 'y',
tooltipDimension: boxHeight,
viewBoxDimension: viewBox.height,
});
} else {
outerStyle.visibility = 'hidden';
}
if (position && isNumber(position.x) && isNumber(position.y)) {
translateX = position.x;
translateY = position.y;
} else {
const { boxWidth, boxHeight } = this.state;

outerStyle = {
...translateStyle({
transform: useTranslate3d
? `translate3d(${translateX}px, ${translateY}px, 0)`
: `translate(${translateX}px, ${translateY}px)`,
}),
...outerStyle,
};
if (boxWidth > 0 && boxHeight > 0 && coordinate) {
translateX = this.getTranslate({
key: 'x',
tooltipDimension: boxWidth,
viewBoxDimension: viewBox.width,
});

translateY = this.getTranslate({
key: 'y',
tooltipDimension: boxHeight,
viewBoxDimension: viewBox.height,
});
} else {
outerStyle.visibility = 'hidden';
}
}

if (isAnimationActive && active) {
outerStyle = {
...translateStyle({
transition: `transform ${animationDuration}ms ${animationEasing}`,
transform: this.props.useTranslate3d
? `translate3d(${translateX}px, ${translateY}px, 0)`
: `translate(${translateX}px, ${translateY}px)`,
}),
...outerStyle,
};
}

const cls = classNames(CLS_PREFIX, {
[`${CLS_PREFIX}-right`]: isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX >= coordinate.x,
[`${CLS_PREFIX}-left`]: isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX < coordinate.x,
[`${CLS_PREFIX}-bottom`]:
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY >= coordinate.y,
[`${CLS_PREFIX}-top`]: isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY < coordinate.y,
});

return (
// ESLint is disabled to allow listening to the `Escape` key. Refer to
// https://github.com/recharts/recharts/pull/2925
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<div tabIndex={-1} role="dialog" className={cls} style={outerStyle} ref={wrapperNode}>
{renderContent(content, {
...props,
payload: finalPayload,
})}
</div>
);
};
if (isAnimationActive && active) {
outerStyle = {
...translateStyle({
transition: `transform ${animationDuration}ms ${animationEasing}`,
}),
...outerStyle,
};
}

// needs to be set so that renderByOrder can find the correct handler function
Tooltip.displayName = 'Tooltip';
const cls = classNames(CLS_PREFIX, {
[`${CLS_PREFIX}-right`]:
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX >= coordinate.x,
[`${CLS_PREFIX}-left`]: isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX < coordinate.x,
[`${CLS_PREFIX}-bottom`]:
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY >= coordinate.y,
[`${CLS_PREFIX}-top`]: isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY < coordinate.y,
});

/**
* needs to be set so that renderByOrder can access an have default values for
* children.props when there are no props set by the consumer
* doesn't work if using default parameters
*/
Tooltip.defaultProps = tooltipDefaultProps;
return (
// ESLint is disabled to allow listening to the `Escape` key. Refer to
// https://github.com/recharts/recharts/pull/2925
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<div
tabIndex={-1}
role="dialog"
className={cls}
style={outerStyle}
ref={node => {
this.wrapperNode = node;
}}
>
{renderContent(content, { ...this.props, payload: finalPayload })}
</div>
);
}
}
Loading

0 comments on commit f4a950b

Please sign in to comment.