Navigation Menu

Skip to content

Commit

Permalink
feat(Popover): add default toggleable fade support (#1364) (#1364)
Browse files Browse the repository at this point in the history
adds propType `fade=true` to PopperContent used in Tooltip and Popover.
Default hide delay is changed from 250 to 0 to match bootstrap.
popperClassName, used for displaying the popper, is separated from classNames.

BREAKING CHANGE: Popover and Tooltip will now fade in and out (like bootstrap's default). To get the previous behavior use fade={false}

Closes #363
  • Loading branch information
esoh authored and TheSharpieOne committed Jan 16, 2019
1 parent e341137 commit ee15c86
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 19 deletions.
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

0 comments on commit ee15c86

Please sign in to comment.