Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit e093a2a

Browse files
authored
feat: usePopper Hook (#343)
* feat: expose usePopper hook * docs: fix demo warnings * test: InnerPopper => Popper * fix: usePopper fixes * feat: attempt to rewrite render-props implementation with hooks * refactor: migrate Manager to hooks * fix: remove @babel/runtime dependency * fix: useMemo dependencies * chore: add bundleSize rollup plugin * fix: simplify build, fix tests, add hook tests, replace enzyme * fix: useIsomorphicLayoutEffect * docs: fix demo arrows * docs: add usePopper documentation * fix: prevent loop renders
1 parent fc13b38 commit e093a2a

25 files changed

+2030
-1882
lines changed

.babelrc

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,16 @@
44
"@babel/flow",
55
"@babel/react"
66
],
7-
"plugins": [
8-
"@babel/proposal-class-properties"
9-
],
107
"env": {
118
"esm": {
12-
"plugins": [
13-
"@babel/transform-runtime"
14-
],
15-
"ignore": [
16-
"**/*.test.js",
17-
"**/__mocks__/**"
18-
]
9+
"ignore": ["**/*.test.js", "**/__mocks__/**"]
1910
},
2011
"cjs": {
21-
"plugins": [
22-
"@babel/transform-runtime",
23-
"@babel/transform-modules-commonjs"
24-
],
25-
"ignore": [
26-
"**/*.test.js",
27-
"**/__mocks__/**"
28-
]
12+
"plugins": [["@babel/transform-modules-commonjs", { "noInterop": true }]],
13+
"ignore": ["**/*.test.js", "**/__mocks__/**"]
2914
},
3015
"test": {
31-
"plugins": [
32-
"@babel/transform-modules-commonjs"
33-
]
16+
"plugins": ["@babel/transform-modules-commonjs"]
3417
}
3518
}
3619
}

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ module.exports = {
44
env: {
55
browser: true,
66
},
7-
plugins: ['react'],
7+
extends: ['plugin:react-hooks/recommended'],
8+
plugins: ['react', 'react-hooks'],
89
rules: {
910
'no-unused-vars': 'error',
1011
'react/jsx-uses-vars': 'error',

.prettierrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
22
singleQuote: true,
33
trailingComma: 'es5',
4+
proseWrap: 'always',
45
};

README.md

Lines changed: 161 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## React Popper
1+
# React Popper
22

33
[![Build Status](https://travis-ci.org/popperjs/react-popper.svg?branch=master)](https://travis-ci.org/popperjs/react-popper)
44
[![npm version](https://img.shields.io/npm/v/react-popper.svg)](https://www.npmjs.com/package/react-popper)
@@ -7,11 +7,12 @@
77
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
88
[![Get support or discuss](https://img.shields.io/badge/chat-on_spectrum-6833F9.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyBpZD0iTGl2ZWxsb18xIiBkYXRhLW5hbWU9IkxpdmVsbG8gMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTAgOCI%2BPGRlZnM%2BPHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU%2BPC9kZWZzPjx0aXRsZT5zcGVjdHJ1bTwvdGl0bGU%2BPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNSwwQy40MiwwLDAsLjYzLDAsMy4zNGMwLDEuODQuMTksMi43MiwxLjc0LDMuMWgwVjcuNThhLjQ0LjQ0LDAsMCwwLC42OC4zNUw0LjM1LDYuNjlINWM0LjU4LDAsNS0uNjMsNS0zLjM1UzkuNTgsMCw1LDBaTTIuODMsNC4xOGEuNjMuNjMsMCwxLDEsLjY1LS42M0EuNjQuNjQsMCwwLDEsMi44Myw0LjE4Wk01LDQuMThhLjYzLjYzLDAsMSwxLC42NS0uNjNBLjY0LjY0LDAsMCwxLDUsNC4xOFptMi4xNywwYS42My42MywwLDEsMSwuNjUtLjYzQS42NC42NCwwLDAsMSw3LjE3LDQuMThaIi8%2BPC9zdmc%2B)](https://spectrum.chat/popper-js/react-popper)
99

10-
React wrapper around [Popper.js](https://popper.js.org).
10+
React wrapper around [Popper](https://popper.js.org).
1111

12-
**important note:** popper.js is **not** a tooltip library, it's a _positioning engine_ to be used to build features such as (but not restricted to) tooltips.
12+
**important note:** Popper is **not** a tooltip library, it's a _positioning
13+
engine_ to be used to build features such as (but not restricted to) tooltips.
1314

14-
## Install
15+
# Install
1516

1617
Via package managers:
1718

@@ -21,19 +22,61 @@ npm install react-popper @popperjs/core --save
2122
yarn add react-popper @popperjs/core
2223
```
2324

24-
**Note:** `@popperjs/core` must be installed in your project in order for `react-popper` to work.
25+
**Note:** `@popperjs/core` must be installed in your project in order for
26+
`react-popper` to work.
2527

2628
Via `script` tag (UMD library exposed as `ReactPopper`):
2729

2830
```html
2931
<script src="https://unpkg.com/react-popper/dist/index.umd.js"></script>
3032
```
3133

32-
## Usage
34+
# Usage
3335

34-
> Using `react-popper@0.x`? You can find its documentation [clicking here](https://github.com/souporserious/react-popper/tree/v0.x)
36+
> Using `react-popper@0.x`? You can find its documentation
37+
> [clicking here](https://github.com/souporserious/react-popper/tree/v0.x)
3538
36-
Example:
39+
`react-popper` provides two different APIs to help consume it:
40+
41+
## React Hooks
42+
43+
The `usePopper` hook can be used to quickly initialize a Popper, it requires a
44+
basic understanding of how the
45+
[React Hooks](https://reactjs.org/docs/hooks-overview.html) work.
46+
47+
```jsx
48+
import { useState } from 'react';
49+
import { usePopper } from 'react-popper';
50+
51+
const Example = () => {
52+
const [referenceElement, setReferenceElement] = useState(null);
53+
const [popperElement, setPopperElement] = useState(null);
54+
const [arrowElement, setArrowElement] = useState(null);
55+
const { styles, attributes } = usePopper(referenceElement, popperElement, {
56+
modifiers: [{ name: 'arrow', options: { element: arrowElement } }],
57+
});
58+
59+
return (
60+
<>
61+
<button type="button" ref={setReferenceElement}>
62+
Reference element
63+
</button>
64+
65+
<div ref={setPopperElement} style={styles.popper} {...attributes.popper}>
66+
Popper element
67+
<div ref={setArrowElement} style={styles.arrow} />
68+
</div>
69+
</>
70+
);
71+
};
72+
```
73+
74+
## Render Props
75+
76+
The `Manager`, `Reference` and `Popper` render-props components offer an
77+
alternative API to initialize a Popper instance, they require a basic
78+
understanding of how the
79+
[React Render Props](https://reactjs.org/docs/render-props.html) work.
3780

3881
```jsx
3982
import { Manager, Reference, Popper } from 'react-popper';
@@ -59,19 +102,60 @@ const Example = () => (
59102
);
60103
```
61104

62-
`react-popper` makes use of a React pattern called **"render prop"**, if you are not
63-
familiar with it, please read more [on the official React documentation](https://reactjs.org/docs/render-props.html).
105+
## API documentation
106+
107+
### Hooks
108+
109+
The `usePopper` hook provides an API almost identical to the ones of
110+
[`createPopper`](https://popper.js.org/docs/v2/constructors/#createpopper)
111+
constructor.
112+
113+
Rather than returning a Popper instance, it will return an object containing the
114+
following properties:
115+
116+
##### `styles`
117+
118+
The `styles` property is an object, its properties are `popper`, and `arrow`.
119+
The two properties are a
120+
[`CSSStyleDeclaration` interface](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration)
121+
describing the necessary CSS properties needed to properly position the two
122+
elements.
123+
124+
You can directly assign the value of the two properties to the React `style`
125+
property of your components.
126+
127+
```js
128+
<div style={styles.popper} />
129+
```
130+
131+
##### `attributes`
64132

65-
> Using React <=15 or Preact? The components created with them don't support to return
66-
> [fragments](https://reactjs.org/docs/fragments.html), this means that you will need to
67-
> wrap `<Reference />` and `<Popper />` into a single, common, `<div />` to make `react-popper` work.
133+
Similar to `styles`, the `attributes` object lists the `popper` and `arrow` HTML
134+
attributes, by default, only `popper` will hold some attributes (e.g.
135+
`data-popper-placement`), but more generically, any HTML attribute described by
136+
the Popper documentation will be available inside these properties.
68137

69-
### API documentation
138+
The easiest way to consume their values is by destructuring them directly onto
139+
your React component.
70140

71-
The `Manager` component is a simple wrapper that needs to surround all the other `react-popper` components in order
72-
to make them communicate with each others.
141+
```js
142+
<div {...attributes.popper} />
143+
```
144+
145+
##### `update`, `forceUpdate`, and `state`
146+
147+
These properties match the ones described in the
148+
[Popper docs](https://popper.js.org/docs/v2/constructors/#types), the only
149+
difference is that they can be `null` if Popper isn't yet been initialized or
150+
has been destroyed.
73151

74-
The `Popper` component accepts the properties `children`, `placement`, `modifiers` and `strategy`.
152+
### Render Props
153+
154+
The `Manager` component is a simple wrapper that needs to surround all the other
155+
`react-popper` components in order to make them communicate with each others.
156+
157+
The `Popper` component accepts the properties `children`, `placement`,
158+
`modifiers` and `strategy`.
75159

76160
```jsx
77161
<Popper
@@ -102,19 +186,36 @@ children: ({|
102186
|}) => Node
103187
```
104188
105-
A function (render prop) that takes as argument an object containing the following properties:
106-
107-
- **`ref`**: used to retrieve the [React refs](https://reactjs.org/docs/refs-and-the-dom.html) of the **popper** element.
108-
- **`style`**: contains the necessary CSS styles (React CSS properties) which are computed by Popper.js to correctly position the **popper** element.
109-
- **`placement`**: describes the placement of your popper after Popper.js has applied all the modifiers
110-
that may have flipped or altered the originally provided `placement` property. You can use this to alter the
111-
style of the popper and or of the arrow according to the definitive placement. For instance, you can use this
112-
property to orient the arrow to the right direction.
113-
- **`isReferenceHidden`**: a boolean signifying the reference element is fully clipped and hidden from view.
114-
- **`hasPopperEscaped`**: a boolean signifying the popper escapes the reference element's boundary (and so it appears detached).
115-
- **`update`**: a function you can ask Popper to recompute your tooltip's position . It will directly call the [Popper#update](https://popper.js.org/docs/v2/lifecycle/#manual-update) method.
116-
- **`arrowProps`**: an object, containing `style` and `ref` properties that are identical to the
117-
ones provided as the first and second arguments of `children`, but relative to the **arrow** element. The `style` property contains `left` and `top` offset values, which are used to center the arrow within the popper. These values can be merged with further custom styling and positioning. See [the demo](https://github.com/FezVrasta/react-popper/blob/8994933c430e48ab62e71495be71e4f440b48a5a/demo/styles.js#L100) for an example.
189+
A function (render prop) that takes as argument an object containing the
190+
following properties:
191+
192+
- **`ref`**: used to retrieve the
193+
[React refs](https://reactjs.org/docs/refs-and-the-dom.html) of the **popper**
194+
element.
195+
- **`style`**: contains the necessary CSS styles (React CSS properties) which
196+
are computed by Popper.js to correctly position the **popper** element.
197+
- **`placement`**: describes the placement of your popper after Popper.js has
198+
applied all the modifiers that may have flipped or altered the originally
199+
provided `placement` property. You can use this to alter the style of the
200+
popper and or of the arrow according to the definitive placement. For
201+
instance, you can use this property to orient the arrow to the right
202+
direction.
203+
- **`isReferenceHidden`**: a boolean signifying the reference element is fully
204+
clipped and hidden from view.
205+
- **`hasPopperEscaped`**: a boolean signifying the popper escapes the reference
206+
element's boundary (and so it appears detached).
207+
- **`update`**: a function you can ask Popper to recompute your tooltip's
208+
position . It will directly call the
209+
[Popper#update](https://popper.js.org/docs/v2/lifecycle/#manual-update)
210+
method.
211+
- **`arrowProps`**: an object, containing `style` and `ref` properties that are
212+
identical to the ones provided as the first and second arguments of
213+
`children`, but relative to the **arrow** element. The `style` property
214+
contains `left` and `top` offset values, which are used to center the arrow
215+
within the popper. These values can be merged with further custom styling and
216+
positioning. See
217+
[the demo](https://github.com/FezVrasta/react-popper/blob/8994933c430e48ab62e71495be71e4f440b48a5a/demo/styles.js#L100)
218+
for an example.
118219
119220
##### `innerRef`
120221
@@ -130,33 +231,41 @@ Function that can be used to obtain popper reference
130231
placement?: PopperJS$Placement;
131232
```
132233
133-
One of the accepted placement values listed in the [Popper.js documentation](https://popper.js.org/popper-documentation.html#Popper.placements).
234+
One of the accepted placement values listed in the
235+
[Popper.js documentation](https://popper.js.org/popper-documentation.html#Popper.placements).
134236
Your popper is going to be placed according to the value of this property.
135237
Defaults to `bottom`.
136238
137239
##### `strategy`
138240
139-
Describes the positioning strategy to use. By default, it is `absolute`, which in the simplest cases does not require repositioning of the popper. If your reference element is in a `fixed` container, use the `fixed` strategy. [Read More](https://popper.js.org/docs/v2/constructors/#strategy)
241+
Describes the positioning strategy to use. By default, it is `absolute`, which
242+
in the simplest cases does not require repositioning of the popper. If your
243+
reference element is in a `fixed` container, use the `fixed` strategy.
244+
[Read More](https://popper.js.org/docs/v2/constructors/#strategy)
140245
141246
##### `modifiers`
142247
143248
```js
144249
modifiers?: PopperJS$Modifiers;
145250
```
146251
147-
An object containing custom settings for the [Popper.js modifiers](https://popper.js.org/docs/v2/modifiers/).
148-
You can use this property to override their settings or to inject your custom ones.
252+
An object containing custom settings for the
253+
[Popper.js modifiers](https://popper.js.org/docs/v2/modifiers/).
254+
You can use this property to override their settings or to inject your custom
255+
ones.
149256
150257
## Usage with `ReactDOM.createPortal`
151258
152-
Popper.js is smart enough to work even if the **popper** and **reference** elements aren't
153-
in the same DOM context.
154-
This means that you can use [`ReactDOM.createPortal`](https://reactjs.org/docs/portals.html)
155-
(or any pre React 16 alternative) to move the popper component somewhere else in the DOM.
259+
Popper.js is smart enough to work even if the **popper** and **reference**
260+
elements aren't in the same DOM context.
261+
This means that you can use
262+
[`ReactDOM.createPortal`](https://reactjs.org/docs/portals.html) (or any pre
263+
React 16 alternative) to move the popper component somewhere else in the DOM.
156264
157-
This can be useful if you want to position a tooltip inside an `overflow: hidden` container
158-
that you want to make overflow. Please note that you can also try the `positionFixed` strategy
159-
to obtain a similar effect with less hassle.
265+
This can be useful if you want to position a tooltip inside an
266+
`overflow: hidden` container that you want to make overflow. Please note that
267+
you can also try the `positionFixed` strategy to obtain a similar effect with
268+
less hassle.
160269
161270
```jsx
162271
import { Manager, Reference, Popper } from 'react-popper';
@@ -186,11 +295,17 @@ const Example = () => (
186295
187296
## Usage without a reference `HTMLElement`
188297
189-
Whenever you need to position a popper based on some arbitrary coordinates, you can provide `Popper` with a `referenceElement` property that is going to be used in place of the `referenceProps.getRef` React ref.
298+
Whenever you need to position a popper based on some arbitrary coordinates, you
299+
can provide `Popper` with a `referenceElement` property that is going to be used
300+
in place of the `referenceProps.getRef` React ref.
190301
191-
The `referenceElement` property must be an object with an interface compatible with an `HTMLElement` as described in the [Popper.js virtualElement documentation](https://popper.js.org/docs/v2/virtual-elements/), this implies that you may also provide a real HTMLElement if needed.
302+
The `referenceElement` property must be an object with an interface compatible
303+
with an `HTMLElement` as described in the
304+
[Popper.js virtualElement documentation](https://popper.js.org/docs/v2/virtual-elements/),
305+
this implies that you may also provide a real HTMLElement if needed.
192306
193-
If `referenceElement` is defined, it will take precedence over any `referenceProps.ref` provided refs.
307+
If `referenceElement` is defined, it will take precedence over any
308+
`referenceProps.ref` provided refs.
194309
195310
```jsx
196311
import { Popper } from 'react-popper';
@@ -231,8 +346,8 @@ const Example = () => (
231346
232347
This library is built with Flow but it supports TypeScript as well.
233348
234-
You can find the exported Flow types in `src/index.js`, and the
235-
TypeScript definitions in `typings/react-popper.d.ts`.
349+
You can find the exported Flow types in `src/index.js`, and the TypeScript
350+
definitions in `typings/react-popper.d.ts`.
236351
237352
## Running Locally
238353

demo/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ const Demo = () => {
117117
<Manager>
118118
<Reference>
119119
{({ ref }) => (
120-
<ReferenceBox innerRef={ref}>
120+
<ReferenceBox ref={ref}>
121121
<a
122122
href="https://github.com/FezVrasta/react-popper"
123123
target="_blank"
@@ -130,10 +130,10 @@ const Demo = () => {
130130
<PoppersContainer>
131131
<Popper placement={activePlacement} modifiers={mainModifiers}>
132132
{({ ref, style, placement, arrowProps }) => (
133-
<TransitionedPopperBox innerRef={ref} style={style}>
133+
<TransitionedPopperBox ref={ref} style={style}>
134134
{placement}
135135
<Arrow
136-
innerRef={arrowProps.ref}
136+
ref={arrowProps.ref}
137137
data-placement={placement}
138138
style={arrowProps.style}
139139
/>
@@ -146,7 +146,7 @@ const Demo = () => {
146146
<Popper placement={p} key={p} modifiers={dotModifiers}>
147147
{({ ref, style }) => (
148148
<PopperDot
149-
innerRef={ref}
149+
ref={ref}
150150
style={style}
151151
onClick={() => setActivePlacement(p)}
152152
title={p}
@@ -163,7 +163,7 @@ const Demo = () => {
163163
{({ ref }) => (
164164
<ClickableReferenceBox
165165
tabIndex={0}
166-
innerRef={ref}
166+
ref={ref}
167167
onClick={() => togglePopper2(!isPopper2Open)}
168168
>
169169
Click to toggle

0 commit comments

Comments
 (0)