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 fade support for ToolTip and Popover (#363) #1364

Merged
merged 1 commit into from Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/Popover.js
Expand Up @@ -11,8 +11,7 @@ const defaultProps = {
const Popover = (props) => {
const popperClasses = classNames(
'popover',
'show',
props.className
'show'
);

const classes = classNames(
Expand All @@ -24,7 +23,7 @@ const Popover = (props) => {
return (
<TooltipPopoverWrapper
{...props}
className={popperClasses}
popperClassName={popperClasses}
innerClassName={classes}
/>
);
Expand Down
60 changes: 51 additions & 9 deletions src/PopperContent.js
Expand Up @@ -4,10 +4,13 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { Arrow, Popper as ReactPopper } from 'react-popper';
import { getTarget, targetPropType, mapToCssModules, DOMElement, tagPropType } from './utils';
import Fade from './Fade';

function noop() { }

const propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
popperClassName: PropTypes.string,
placement: PropTypes.string,
placementPrefix: PropTypes.string,
arrowClassName: PropTypes.string,
Expand All @@ -22,6 +25,9 @@ const propTypes = {
target: targetPropType.isRequired,
modifiers: PropTypes.object,
boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]),
onClosed: PropTypes.func,
fade: PropTypes.bool,
transition: PropTypes.shape(Fade.propTypes),
};

const defaultProps = {
Expand All @@ -34,6 +40,11 @@ const defaultProps = {
flip: true,
container: 'body',
modifiers: {},
onClosed: noop,
fade: true,
transition: {
...Fade.defaultProps,
}
};

const childContextTypes = {
Expand All @@ -48,7 +59,8 @@ class PopperContent extends React.Component {
this.setTargetNode = this.setTargetNode.bind(this);
this.getTargetNode = this.getTargetNode.bind(this);
this.getRef = this.getRef.bind(this);
this.state = {};
this.onClosed = this.onClosed.bind(this);
this.state = { isOpen: props.isOpen };
}

getChildContext() {
Expand All @@ -60,6 +72,13 @@ class PopperContent extends React.Component {
};
}

static getDerivedStateFromProps(props, state) {
if (props.isOpen && !state.isOpen) {
return { isOpen: props.isOpen };
}
else return null;
}

componentDidUpdate() {
if (this._element && this._element.childNodes && this._element.childNodes[0] && this._element.childNodes[0].focus) {
this._element.childNodes[0].focus();
Expand Down Expand Up @@ -89,6 +108,11 @@ class PopperContent extends React.Component {
return data;
}

onClosed() {
this.props.onClosed();
this.setState({ isOpen: false });
}

renderChildren() {
const {
cssModule,
Expand All @@ -101,11 +125,14 @@ class PopperContent extends React.Component {
placementPrefix,
arrowClassName: _arrowClassName,
hideArrow,
className,
popperClassName: _popperClassName,
tag,
container,
modifiers,
boundariesElement,
onClosed,
fade,
transition,
...attrs
} = this.props;
const arrowClassName = mapToCssModules(classNames(
Expand All @@ -114,7 +141,7 @@ class PopperContent extends React.Component {
), cssModule);
const placement = (this.state.placement || attrs.placement).split('-')[0];
const popperClassName = mapToCssModules(classNames(
className,
_popperClassName,
placementPrefix ? `${placementPrefix}-${placement}` : placement
), this.props.cssModule);

Expand All @@ -130,18 +157,33 @@ class PopperContent extends React.Component {
...modifiers,
};

const popperTransition = {
...Fade.defaultProps,
...transition,
baseClass: fade ? transition.baseClass : '',
timeout: fade ? transition.timeout : 0,
}

return (
<ReactPopper modifiers={extendedModifiers} {...attrs} component={tag} className={popperClassName} x-placement={this.state.placement || attrs.placement}>
{children}
{!hideArrow && <Arrow className={arrowClassName} />}
</ReactPopper>
<Fade
{...popperTransition}
{...attrs}
in={isOpen}
onExited={this.onClosed}
tag={tag}
>
<ReactPopper modifiers={extendedModifiers} className={popperClassName} x-placement={this.state.placement || attrs.placement} placement={this.state.placement || attrs.placement}>
{children}
{!hideArrow && <Arrow className={arrowClassName} />}
</ReactPopper>
</Fade>
);
}

render() {
this.setTargetNode(getTarget(this.props.target));

if (this.props.isOpen) {
if (this.state.isOpen) {
return this.props.container === 'inline' ?
this.renderChildren() :
ReactDOM.createPortal((<div ref={this.getRef}>{this.renderChildren()}</div>), this.getContainerNode());
Expand Down
5 changes: 2 additions & 3 deletions src/Tooltip.js
Expand Up @@ -12,8 +12,7 @@ const defaultProps = {
const Tooltip = (props) => {
const popperClasses = classNames(
'tooltip',
'show',
props.className
'show'
);

const classes = classNames(
Expand All @@ -25,7 +24,7 @@ const Tooltip = (props) => {
return (
<TooltipPopoverWrapper
{...props}
className={popperClasses}
popperClassName={popperClasses}
innerClassName={classes}
/>
);
Expand Down
32 changes: 28 additions & 4 deletions src/TooltipPopoverWrapper.js
Expand Up @@ -21,6 +21,7 @@ export const propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
arrowClassName: PropTypes.string,
popperClassName: PropTypes.string,
cssModule: PropTypes.object,
toggle: PropTypes.func,
autohide: PropTypes.bool,
Expand All @@ -37,11 +38,12 @@ export const propTypes = {
PropTypes.object
]),
trigger: PropTypes.string,
fade: PropTypes.bool,
};

const DEFAULT_DELAYS = {
show: 0,
hide: 250
hide: 0
};

const defaultProps = {
Expand All @@ -51,6 +53,7 @@ const defaultProps = {
delay: DEFAULT_DELAYS,
toggle: function () {},
trigger: 'click',
fade: true,
};

function isInDOMSubtree(element, subtreeRoot) {
Expand All @@ -76,6 +79,8 @@ class TooltipPopoverWrapper extends React.Component {
this.hide = this.hide.bind(this);
this.onEscKeyDown = this.onEscKeyDown.bind(this);
this.getRef = this.getRef.bind(this);
this.onClosed = this.onClosed.bind(this);
this.state = { isOpen: props.isOpen };
}

componentDidMount() {
Expand All @@ -86,11 +91,21 @@ class TooltipPopoverWrapper extends React.Component {
this.removeTargetEvents();
}

static getDerivedStateFromProps(props, state) {
if (props.isOpen && !state.isOpen) {
return { isOpen: props.isOpen };
}
else return null;
}

onMouseOverTooltipContent() {
if (this.props.trigger.indexOf('hover') > -1 && !this.props.autohide) {
if (this._hideTimeout) {
this.clearHideTimeout();
}
if (this.state.isOpen && !this.props.isOpen) {
this.toggle();
}
}
}

Expand Down Expand Up @@ -274,8 +289,12 @@ class TooltipPopoverWrapper extends React.Component {
return this.props.toggle(e);
}

onClosed() {
this.setState({ isOpen: false });
}

render() {
if (!this.props.isOpen) {
if (!this.state.isOpen) {
return null;
}

Expand All @@ -292,31 +311,36 @@ class TooltipPopoverWrapper extends React.Component {
placement,
placementPrefix,
arrowClassName,
popperClassName,
container,
modifiers,
offset,
fade,
} = this.props;

const attributes = omit(this.props, Object.keys(propTypes));

const popperClasses = mapToCssModules(className, cssModule);
const popperClasses = mapToCssModules(popperClassName, cssModule);

const classes = mapToCssModules(innerClassName, cssModule);

return (
<PopperContent
className={popperClasses}
className={className}
target={target}
isOpen={isOpen}
hideArrow={hideArrow}
boundariesElement={boundariesElement}
placement={placement}
placementPrefix={placementPrefix}
arrowClassName={arrowClassName}
popperClassName={popperClasses}
container={container}
modifiers={modifiers}
offset={offset}
cssModule={cssModule}
onClosed={this.onClosed}
fade={fade}
>
<div
{...attributes}
Expand Down
23 changes: 23 additions & 0 deletions src/__tests__/TooltipPopoverWrapper.spec.js
Expand Up @@ -125,10 +125,33 @@ describe('Tooltip', () => {
{ attachTo: container }
);

jest.runTimersToTime(150);
expect(document.getElementsByClassName('tooltip').length).toBe(0);

wrapper.setProps({ isOpen: true });
jest.runTimersToTime(150);
expect(document.getElementsByClassName('tooltip').length).toBe(1);

wrapper.setProps({ isOpen: false });
jest.runTimersToTime(150);
expect(document.getElementsByClassName('tooltip').length).toBe(0);
wrapper.detach();
});

it('should toggle isOpen', () => {
const wrapper = mount(
<TooltipPopoverWrapper target="target" isOpen={isOpen} toggle={toggle} className="tooltip show" fade={false}>
Tooltip Content
</TooltipPopoverWrapper>,
{ attachTo: container }
);

expect(document.getElementsByClassName('tooltip').length).toBe(0);
wrapper.setProps({ isOpen: true });
jest.runTimersToTime(0); // slight async delay for getDerivedStateFromProps to update isOpen
expect(document.getElementsByClassName('tooltip').length).toBe(1);
wrapper.setProps({ isOpen: false });
jest.runTimersToTime(0);
expect(document.getElementsByClassName('tooltip').length).toBe(0);
wrapper.detach();
});
Expand Down