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(TooltipPopoverWrapper): scheduleUpdate as render prop #1792

Merged
merged 3 commits into from Jun 22, 2020
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
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();
});
});
});