Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 (
<div>
<div
ref={(div) => this.target = div}
style={{ width: 120, height: 120, background: '#b4da55' }}
onClick={this.handleClick}
>
Click {this.state.isOpen ? 'to hide' : 'to show'} popper
</div>
{this.state.isOpen && (
<Popper className="popper" target={this.target}>
Popper Content
<Arrow className="popper__arrow"/>
</Popper>
)}
</div>
)
}
}
```

## `Shared Props`

`Target`, `Popper`, and `Arrow` all share the following props
Expand Down Expand Up @@ -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.

Expand Down
12 changes: 12 additions & 0 deletions example/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -18,6 +21,15 @@ const App = () => (
<div style={{ marginBottom: 200 }}>
<ModifiersExample />
</div>
<div style={{ marginBottom: 200 }}>
<ToggleableExample />
</div>
<div style={{ marginBottom: 200 }}>
<StandaloneExample />
</div>
<div style={{ marginBottom: 200 }}>
<StandaloneObjectExample />
</div>
</div>
)

Expand Down
37 changes: 37 additions & 0 deletions example/standalone.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h2>Standalone Popper Example</h2>
<div
ref={div => (this.target = div)}
style={{ width: 120, height: 120, background: '#b4da55' }}
onClick={this.handleClick}
>
Click {this.state.isOpen ? 'to hide' : 'to show'} popper
</div>
{this.state.isOpen && (
<Popper className="popper" target={this.target}>
Popper Content for Standalone example
<Arrow className="popper__arrow" />
</Popper>
)}
</div>
)
}
}

export default StandaloneExample
48 changes: 48 additions & 0 deletions example/standaloneObject.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h2>Standalone referenceObject Popper Example</h2>
<div
style={{ width: 120, height: 120, background: '#b4da55' }}
onClick={this.handleClick}
>
Click {this.state.isOpen ? 'to hide' : 'to show'} popper
</div>
{this.state.isOpen && (
<Popper className="popper" target={reference}>
Popper Content for Standalone example
<Arrow className="popper__arrow" />
</Popper>
)}
</div>
)
}
}

export default StandaloneObjectExample
38 changes: 38 additions & 0 deletions example/toggleable.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h2>Toggleable Popper Example</h2>
<Manager>
<Target
style={{ width: 120, height: 120, background: '#b4da55' }}
onClick={this.handleClick}
>
Click {this.state.isOpen ? 'to hide' : 'to show'} popper
</Target>
{this.state.isOpen && (
<Popper className="popper">
Popper Content for Toggleable Example
<Arrow className="popper__arrow" />
</Popper>
)}
</Manager>
</div>
)
}
}

export default ToggleableExample
23 changes: 21 additions & 2 deletions src/Popper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PopperJS from 'popper.js'

class Popper extends Component {
static contextTypes = {
popperManager: PropTypes.object.isRequired,
popperManager: PropTypes.object,
}

static childContextTypes = {
Expand All @@ -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 = {
Expand All @@ -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()
Expand All @@ -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()
}

Expand Down