diff --git a/README.md b/README.md index 22df069..2b8e2e8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ const PopperExample = () => ( ) ``` -## Usage w/ child function +## Usage with child function This is a useful way to interact with custom components. Just make sure you pass down the refs properly. @@ -77,6 +77,49 @@ const PopperExample = () => ( ) ``` +## Usage without Manager + +It's generally easiest to let the `Manager` and `Target` components handle passing the target DOM element to the `Popper` component. However, you can pass a target [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or a [referenceObject](https://popper.js.org/popper-documentation.html#referenceObject) directly into `Popper` if you need to. + +Handling DOM Elements from React can be complicated. The `Manager` and `Target` components handle these complexities for you, so their use is strongly recommended when using DOM Elements. + +```js +import { PureComonent } from 'react' +import { Popper, Arrow } from 'react-popper' + +class StandaloneExample extends PureComponent { + state = { + isOpen: false, + } + + handleClick() = () => { + this.setState(prevState => ({ + isOpen: !prevState.isOpen + })) + } + + render() { + return ( +
+
this.target = div} + style={{ width: 120, height: 120, background: '#b4da55' }} + onClick={this.handleClick} + > + Click {this.state.isOpen ? 'to hide' : 'to show'} popper +
+ {this.state.isOpen && ( + + Popper Content + + + )} +
+ ) + } +} +``` + ## `Shared Props` `Target`, `Popper`, and `Arrow` all share the following props @@ -124,11 +167,12 @@ A `Target`'s child may be one of the following: Your popper that gets attached to the `Target` component. -Each `Popper` must be wrapped in a `Manager`, and each `Manager` can wrap multiple `Popper` components. +Each `Popper` must either be wrapped in a `Manager`, or passed a `target` prop directly. Each `Manager` can wrap multiple `Popper` components. #### `placement`: PropTypes.oneOf(Popper.placements) #### `eventsEnabled`: PropTypes.bool #### `modifiers`: PropTypes.object +#### `target`: PropTypes.oneOfType([PropTypes.instanceOf(Element), Popper.referenceObject]) Passes respective options to a new [Popper instance](https://github.com/FezVrasta/popper.js/blob/master/docs/_includes/popper-documentation.md#new-popperreference-popper-options). As for `onCreate` and `onUpdate`, these callbacks were intentionally left out in favor of using the [component lifecycle methods](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle). If you have a good use case for these please feel free to file and issue and I will consider adding them in. diff --git a/example/index.jsx b/example/index.jsx index 1e27988..f14e5a5 100644 --- a/example/index.jsx +++ b/example/index.jsx @@ -4,6 +4,9 @@ import ReactDOM from 'react-dom' import MultipleExample from './multiple' import AnimatedExample from './animated' import ModifiersExample from './modifiers' +import ToggleableExample from './toggleable' +import StandaloneExample from './standalone' +import StandaloneObjectExample from './standaloneObject' import './main.css' @@ -18,6 +21,15 @@ const App = () => (
+
+ +
+
+ +
+
+ +
) diff --git a/example/standalone.jsx b/example/standalone.jsx new file mode 100644 index 0000000..57c9277 --- /dev/null +++ b/example/standalone.jsx @@ -0,0 +1,37 @@ +import React, { PureComponent } from 'react' +import { Popper, Arrow } from '../src/react-popper' + +class StandaloneExample extends PureComponent { + state = { + isOpen: false, + } + + handleClick = () => { + this.setState(prevState => ({ + isOpen: !prevState.isOpen, + })) + } + + render() { + return ( +
+

Standalone Popper Example

+
(this.target = div)} + style={{ width: 120, height: 120, background: '#b4da55' }} + onClick={this.handleClick} + > + Click {this.state.isOpen ? 'to hide' : 'to show'} popper +
+ {this.state.isOpen && ( + + Popper Content for Standalone example + + + )} +
+ ) + } +} + +export default StandaloneExample diff --git a/example/standaloneObject.jsx b/example/standaloneObject.jsx new file mode 100644 index 0000000..8b7fe6d --- /dev/null +++ b/example/standaloneObject.jsx @@ -0,0 +1,48 @@ +import React, { PureComponent } from 'react' +import { Popper, Arrow } from '../src/react-popper' + +class StandaloneObjectExample extends PureComponent { + state = { + isOpen: false, + } + + handleClick = () => { + this.setState(prevState => ({ + isOpen: !prevState.isOpen, + })) + } + + render() { + const reference = { + getBoundingClientRect: () => ({ + top: 10, + left: 100, + right: 150, + bottom: 90, + width: 50, + height: 80, + }), + clientWidth: 50, + clientHeight: 80, + } + return ( +
+

Standalone referenceObject Popper Example

+
+ Click {this.state.isOpen ? 'to hide' : 'to show'} popper +
+ {this.state.isOpen && ( + + Popper Content for Standalone example + + + )} +
+ ) + } +} + +export default StandaloneObjectExample diff --git a/example/toggleable.jsx b/example/toggleable.jsx new file mode 100644 index 0000000..bf1f706 --- /dev/null +++ b/example/toggleable.jsx @@ -0,0 +1,38 @@ +import React, { PureComponent } from 'react' +import { Manager, Target, Popper, Arrow } from '../src/react-popper' + +class ToggleableExample extends PureComponent { + state = { + isOpen: false, + } + + handleClick = () => { + this.setState(prevState => ({ + isOpen: !prevState.isOpen, + })) + } + + render() { + return ( +
+

Toggleable Popper Example

+ + + Click {this.state.isOpen ? 'to hide' : 'to show'} popper + + {this.state.isOpen && ( + + Popper Content for Toggleable Example + + + )} + +
+ ) + } +} + +export default ToggleableExample diff --git a/src/Popper.jsx b/src/Popper.jsx index df803e3..f7cde22 100644 --- a/src/Popper.jsx +++ b/src/Popper.jsx @@ -4,7 +4,7 @@ import PopperJS from 'popper.js' class Popper extends Component { static contextTypes = { - popperManager: PropTypes.object.isRequired, + popperManager: PropTypes.object, } static childContextTypes = { @@ -18,6 +18,14 @@ class Popper extends Component { eventsEnabled: PropTypes.bool, modifiers: PropTypes.object, children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + target: PropTypes.oneOfType([ + PropTypes.instanceOf(Element), + PropTypes.shape({ + getBoundingClientRect: PropTypes.func.isRequired, + clientWidth: PropTypes.number.isRequired, + clientHeight: PropTypes.number.isRequired, + }), + ]), } static defaultProps = { @@ -41,7 +49,8 @@ class Popper extends Component { componentDidUpdate(lastProps) { if ( lastProps.placement !== this.props.placement || - lastProps.eventsEnabled !== this.props.eventsEnabled + lastProps.eventsEnabled !== this.props.eventsEnabled || + lastProps.target !== this.props.target ) { this._destroyPopper() this._createPopper() @@ -60,6 +69,16 @@ class Popper extends Component { } _getTargetNode = () => { + if (this.props.target) { + return this.props.target + } else if ( + !this.context.popperManager || + !this.context.popperManager.getTargetNode() + ) { + throw new Error( + 'Target missing. Popper must be given a target from the Popper Manager, or as a prop.', + ) + } return this.context.popperManager.getTargetNode() }