Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(TooltipPopoverWrapper): scheduleUpdate as render prop (#1792)
Co-authored-by: Evan Sharp <dumbdrum@gmail.com>
  • Loading branch information
kyletsang and TheSharpieOne committed Jun 22, 2020
1 parent c4f86d5 commit cda6fe9
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 12 deletions.
17 changes: 17 additions & 0 deletions docs/lib/Components/PopoversPage.js
Expand Up @@ -11,6 +11,8 @@ import PopoverFocusExample from '../examples/PopoverFocus';
const PopoverFocusExampleSource = require('!!raw-loader!../examples/PopoverFocus');
import UncontrolledPopoverExample from '../examples/PopoverUncontrolled';
const UncontrolledPopoverExampleSource = require('!!raw-loader!../examples/PopoverUncontrolled');
import PopoverScheduleUpdateExample from '../examples/PopoverScheduleUpdate';
const PopoverScheduleUpdateExampleSource = require('!!raw-loader!../examples/PopoverScheduleUpdate');

export default class PopoversPage extends React.Component {
render() {
Expand All @@ -30,6 +32,7 @@ export default class PopoversPage extends React.Component {
<pre>
<PrismCode className="language-jsx">
{`Popover.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
// space separated list of triggers (e.g. "click hover focus")
trigger: PropTypes.string,
// boolean to control the state of the popover
Expand Down Expand Up @@ -120,6 +123,20 @@ export default class PopoversPage extends React.Component {
{UncontrolledPopoverExampleSource}
</PrismCode>
</pre>
<SectionTitle>Repositioning Popovers</SectionTitle>
<p>
If you need to reposition a popover due to content changes or target placement changes, use
the <code>scheduleUpdate</code> function to manually reposition it. This function is exposed
as a render prop for <code>children</code>.
</p>
<div className="docs-example">
<PopoverScheduleUpdateExample />
</div>
<pre>
<PrismCode className="language-jsx">
{PopoverScheduleUpdateExampleSource}
</PrismCode>
</pre>
</div>
);
}
Expand Down
17 changes: 17 additions & 0 deletions docs/lib/Components/TooltipsPage.js
Expand Up @@ -11,6 +11,8 @@ import TooltipExampleMulti from '../examples/TooltipMulti';
const TooltipExampleMultiSource = require('!!raw-loader!../examples/TooltipMulti');
import TooltipExampleUncontrolled from '../examples/TooltipUncontrolled';
const TooltipExampleUncontrolledSource = require('!!raw-loader!../examples/TooltipUncontrolled');
import TooltipScheduleUpdateExample from '../examples/TooltipScheduleUpdate';
const TooltipScheduleUpdateExampleSource = require('!!raw-loader!../examples/TooltipScheduleUpdate');

export default class TooltipsPage extends React.Component {
render() {
Expand All @@ -30,6 +32,7 @@ export default class TooltipsPage extends React.Component {
<pre>
<PrismCode className="language-jsx">
{`Tooltip.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
// space separated list of triggers (e.g. "click hover focus")
trigger: PropTypes.string,
// boundaries for popper, can be scrollParent, window, viewport, or any DOM element
Expand Down Expand Up @@ -133,6 +136,20 @@ export default class TooltipsPage extends React.Component {
{TooltipExampleUncontrolledSource}
</PrismCode>
</pre>
<SectionTitle>Repositioning Tooltips</SectionTitle>
<p>
If you need to reposition a tooltip due to content changes or target placement changes, use
the <code>scheduleUpdate</code> function to manually reposition it. This function is exposed
as a render prop for <code>children</code>.
</p>
<div className="docs-example">
<TooltipScheduleUpdateExample />
</div>
<pre>
<PrismCode className="language-jsx">
{TooltipScheduleUpdateExampleSource}
</PrismCode>
</pre>
</div>
);
}
Expand Down
37 changes: 37 additions & 0 deletions docs/lib/examples/PopoverScheduleUpdate.js
@@ -0,0 +1,37 @@
import React, { useState } from 'react';
import { Button, UncontrolledPopover, PopoverHeader, PopoverBody, Collapse } from 'reactstrap';

const PopoverContent = ({ scheduleUpdate }) => {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<PopoverHeader>Schedule Update</PopoverHeader>
<PopoverBody>
<Button onClick={() => setIsOpen(!isOpen)}>Click me</Button>
<Collapse isOpen={isOpen} onEntered={scheduleUpdate} onExited={scheduleUpdate}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
</Collapse>
</PopoverBody>
</>
);
}

const Example = (props) => {
return (
<div className="text-center">
<Button id="ScheduleUpdateButton" type="button">
Open Popover
</Button>
<UncontrolledPopover trigger="click" placement="top" target="ScheduleUpdateButton">
{({ scheduleUpdate }) => (
<PopoverContent scheduleUpdate={scheduleUpdate} />
)}
</UncontrolledPopover>
</div>
);
}

export default Example;
37 changes: 37 additions & 0 deletions docs/lib/examples/TooltipScheduleUpdate.js
@@ -0,0 +1,37 @@
import React, { useState, useEffect } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap';

const shortText = 'Hi';
const longText = 'Long tooltip content to test scheduleUpdate';

const TooltipContent = ({ scheduleUpdate }) => {
const [text, setText] = useState(shortText);

useEffect(() => {
const intervalId = setInterval(() => {
setText(text === shortText ? longText : shortText);
scheduleUpdate();
}, 2000);

return () => clearInterval(intervalId);
});

return (
<>{text}</>
);
}

const Example = () => {
return (
<div className="text-center">
<Button id="ScheduleUpdateTooltip">Click me</Button>
<UncontrolledTooltip placement="top" target="ScheduleUpdateTooltip" trigger="click">
{({ scheduleUpdate }) => (
<TooltipContent scheduleUpdate={scheduleUpdate} />
)}
</UncontrolledTooltip>
</div>
);
}

export default Example;
6 changes: 3 additions & 3 deletions src/PopperContent.js
Expand Up @@ -9,7 +9,7 @@ import Fade from './Fade';
function noop() { }

const propTypes = {
children: PropTypes.node.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
popperClassName: PropTypes.string,
placement: PropTypes.string,
placementPrefix: PropTypes.string,
Expand Down Expand Up @@ -151,9 +151,9 @@ class PopperContent extends React.Component {
modifiers={extendedModifiers}
placement={placement}
>
{({ ref, style, placement, outOfBoundaries, arrowProps }) => (
{({ ref, style, placement, outOfBoundaries, arrowProps, scheduleUpdate }) => (
<div ref={ref} style={style} className={popperClassName} x-placement={placement} x-out-of-boundaries={outOfBoundaries ? 'true' : undefined}>
{children}
{typeof children === 'function' ? children({ scheduleUpdate }) : children}
{!hideArrow && <span ref={arrowProps.ref} className={arrowClassName} style={arrowProps.style} />}
</div>
)}
Expand Down
25 changes: 16 additions & 9 deletions src/TooltipPopoverWrapper.js
Expand Up @@ -11,6 +11,7 @@ import {
} from './utils';

export const propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
placement: PropTypes.oneOf(PopperPlacements),
target: targetPropType.isRequired,
container: targetPropType,
Expand Down Expand Up @@ -338,6 +339,7 @@ class TooltipPopoverWrapper extends React.Component {
offset,
fade,
flip,
children
} = this.props;

const attributes = omit(this.props, Object.keys(propTypes));
Expand All @@ -364,15 +366,20 @@ class TooltipPopoverWrapper extends React.Component {
fade={fade}
flip={flip}
>
<div
{...attributes}
ref={this.getRef}
className={classes}
role="tooltip"
onMouseOver={this.onMouseOverTooltipContent}
onMouseLeave={this.onMouseLeaveTooltipContent}
onKeyDown={this.onEscKeyDown}
/>
{({ scheduleUpdate }) => (
<div
{...attributes}
ref={this.getRef}
className={classes}
role="tooltip"
onMouseOver={this.onMouseOverTooltipContent}
onMouseLeave={this.onMouseLeaveTooltipContent}
onKeyDown={this.onEscKeyDown}
>
{typeof children === 'function' ? children({ scheduleUpdate }) : children}
</div>
)}

</PopperContent>
);
}
Expand Down
20 changes: 20 additions & 0 deletions src/__tests__/PopperContent.spec.js
Expand Up @@ -145,4 +145,24 @@ describe('PopperContent', () => {

expect(wrapper.getDOMNode().tagName.toLowerCase()).toBe('main');
});

it('should allow a function to be used as children', () => {
const renderChildren = jest.fn();
const wrapper = mount(
<PopperContent target="target" isOpen>
{renderChildren}
</PopperContent>
);
expect(renderChildren).toHaveBeenCalled();
});

it('should render children properly when children is a function', () => {
const wrapper = mount(
<PopperContent target="target" isOpen>
{() => 'Yo!'}
</PopperContent>
);

expect(wrapper.text()).toBe('Yo!');
});
});
31 changes: 31 additions & 0 deletions src/__tests__/TooltipPopoverWrapper.spec.js
Expand Up @@ -726,5 +726,36 @@ describe('Tooltip', () => {
expect(instance._hideTimeout).toBeFalsy();
wrapper.detach();
});

it('should allow a function to be used as children', () => {
const renderChildren = jest.fn();
const wrapper = mount(
<TooltipPopoverWrapper target="target" isOpen toggle={toggle}>
{renderChildren}
</TooltipPopoverWrapper>
);
expect(renderChildren).toHaveBeenCalled();
});

it('should render children properly when children is a function', () => {
isOpen = true;
const wrapper = mount(
<TooltipPopoverWrapper target="target" isOpen={isOpen} toggle={toggle} className="tooltip show" trigger="hover">
{() => 'Tooltip Content'}
</TooltipPopoverWrapper>,
{ attachTo: container }
);

const Tooltips = document.getElementsByClassName('tooltip');
expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1);
expect(Tooltips.length).toBe(1);
expect(Tooltips[0].textContent).toBe('Tooltip Content');

expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1);
expect(Tooltips.length).toBe(1);
expect(Tooltips[0].textContent).toBe('Tooltip Content');

wrapper.detach();
});
});
});

0 comments on commit cda6fe9

Please sign in to comment.