diff --git a/docs/lib/Components/PopoversPage.js b/docs/lib/Components/PopoversPage.js index f890edfcf..8512d5919 100644 --- a/docs/lib/Components/PopoversPage.js +++ b/docs/lib/Components/PopoversPage.js @@ -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() { @@ -30,6 +32,7 @@ export default class PopoversPage extends React.Component {
           
 {`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
@@ -120,6 +123,20 @@ export default class PopoversPage extends React.Component {
             {UncontrolledPopoverExampleSource}
           
         
+ Repositioning Popovers +

+ If you need to reposition a popover due to content changes or target placement changes, use + the scheduleUpdate function to manually reposition it. This function is exposed + as a render prop for children. +

+
+ +
+
+          
+            {PopoverScheduleUpdateExampleSource}
+          
+        
); } diff --git a/docs/lib/Components/TooltipsPage.js b/docs/lib/Components/TooltipsPage.js index 3d3d1c9d1..de997d934 100644 --- a/docs/lib/Components/TooltipsPage.js +++ b/docs/lib/Components/TooltipsPage.js @@ -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() { @@ -30,6 +32,7 @@ export default class TooltipsPage extends React.Component {
           
 {`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
@@ -133,6 +136,20 @@ export default class TooltipsPage extends React.Component {
             {TooltipExampleUncontrolledSource}
           
         
+ Repositioning Tooltips +

+ If you need to reposition a tooltip due to content changes or target placement changes, use + the scheduleUpdate function to manually reposition it. This function is exposed + as a render prop for children. +

+
+ +
+
+          
+            {TooltipScheduleUpdateExampleSource}
+          
+        
); } diff --git a/docs/lib/examples/PopoverScheduleUpdate.js b/docs/lib/examples/PopoverScheduleUpdate.js new file mode 100644 index 000000000..4d19d112e --- /dev/null +++ b/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 ( + <> + Schedule Update + + + + 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. + + + + ); +} + +const Example = (props) => { + return ( +
+ + + {({ scheduleUpdate }) => ( + + )} + +
+ ); +} + +export default Example; \ No newline at end of file diff --git a/docs/lib/examples/TooltipScheduleUpdate.js b/docs/lib/examples/TooltipScheduleUpdate.js new file mode 100644 index 000000000..957ce650b --- /dev/null +++ b/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 ( +
+ + + {({ scheduleUpdate }) => ( + + )} + +
+ ); +} + +export default Example; \ No newline at end of file diff --git a/src/PopperContent.js b/src/PopperContent.js index d88377d5b..c4e2e945a 100644 --- a/src/PopperContent.js +++ b/src/PopperContent.js @@ -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, @@ -151,9 +151,9 @@ class PopperContent extends React.Component { modifiers={extendedModifiers} placement={placement} > - {({ ref, style, placement, outOfBoundaries, arrowProps }) => ( + {({ ref, style, placement, outOfBoundaries, arrowProps, scheduleUpdate }) => (
- {children} + {typeof children === 'function' ? children({ scheduleUpdate }) : children} {!hideArrow && }
)} diff --git a/src/TooltipPopoverWrapper.js b/src/TooltipPopoverWrapper.js index a07cbf4f0..7d7b4319d 100644 --- a/src/TooltipPopoverWrapper.js +++ b/src/TooltipPopoverWrapper.js @@ -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, @@ -338,6 +339,7 @@ class TooltipPopoverWrapper extends React.Component { offset, fade, flip, + children } = this.props; const attributes = omit(this.props, Object.keys(propTypes)); @@ -364,15 +366,20 @@ class TooltipPopoverWrapper extends React.Component { fade={fade} flip={flip} > -
+ {({ scheduleUpdate }) => ( +
+ {typeof children === 'function' ? children({ scheduleUpdate }) : children} +
+ )} + ); } diff --git a/src/__tests__/PopperContent.spec.js b/src/__tests__/PopperContent.spec.js index 7e987c868..eac8aebc0 100644 --- a/src/__tests__/PopperContent.spec.js +++ b/src/__tests__/PopperContent.spec.js @@ -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( + + {renderChildren} + + ); + expect(renderChildren).toHaveBeenCalled(); + }); + + it('should render children properly when children is a function', () => { + const wrapper = mount( + + {() => 'Yo!'} + + ); + + expect(wrapper.text()).toBe('Yo!'); + }); }); diff --git a/src/__tests__/TooltipPopoverWrapper.spec.js b/src/__tests__/TooltipPopoverWrapper.spec.js index a9476ded0..a960b3260 100644 --- a/src/__tests__/TooltipPopoverWrapper.spec.js +++ b/src/__tests__/TooltipPopoverWrapper.spec.js @@ -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( + + {renderChildren} + + ); + expect(renderChildren).toHaveBeenCalled(); + }); + + it('should render children properly when children is a function', () => { + isOpen = true; + const wrapper = mount( + + {() => 'Tooltip Content'} + , + { 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(); + }); }); });