Skip to content

Commit ea80750

Browse files
committed
Replace Popper with Floating UI
1 parent 7d7ddde commit ea80750

File tree

5 files changed

+108
-84
lines changed

5 files changed

+108
-84
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@
4848
},
4949
"dependencies": {
5050
"@babel/runtime": "^7.14.6",
51-
"@popperjs/core": "^2.10.2",
51+
"@floating-ui/react-dom": "^2.1.2",
5252
"@restart/hooks": "^0.4.0",
5353
"classnames": "^2.2.0",
5454
"fast-deep-equal": "^3.1.1",
5555
"invariant": "^2.2.1",
5656
"lodash.debounce": "^4.0.8",
5757
"prop-types": "^15.5.8",
5858
"react-overlays": "^5.2.0",
59-
"react-popper": "^2.2.5",
6059
"scroll-into-view-if-needed": "^3.1.0",
6160
"warning": "^4.0.1"
6261
},

src/components/Overlay/Overlay.test.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22

3-
import { getModifiers, getPlacement } from './useOverlay';
3+
import { getMiddleware, getPlacement } from './useOverlay';
44
import * as stories from './Overlay.stories';
55

66
import { Align } from '../../types';
@@ -85,29 +85,30 @@ describe('Overlay placement', () => {
8585
});
8686

8787
describe('Overlay modifiers', () => {
88-
it('sets the `flip` modifier', () => {
88+
it('conditionally adds the `flip` middleware', () => {
8989
const props: ModifierProps = { align: 'justify', flip: false };
9090
const selector = ({ name }: Modifier) => name === 'flip';
9191

92-
expect(getModifiers(props).find(selector)?.enabled).toBe(false);
92+
expect(getMiddleware(props).some(selector)).toBe(false);
9393

9494
props.flip = true;
95-
expect(getModifiers(props).find(selector)?.enabled).toBe(true);
95+
expect(getMiddleware(props).some(selector)).toBe(true);
9696
});
9797

98-
it('conditionally adds the `setWidth` modifier', () => {
98+
it('conditionally adds the `size` middleware', () => {
9999
const props: ModifierProps = { align: 'justify', flip: false };
100100

101-
const modifiers = getModifiers(props);
102-
expect(modifiers).toHaveLength(3);
103-
expect(
104-
modifiers.find(({ name }) => name === 'setPopperWidth')
105-
).toBeTruthy();
101+
const middleware = getMiddleware(props);
102+
expect(middleware).toHaveLength(1);
103+
expect(middleware.some(({ name }) => name === 'size')).toBe(true);
106104

107105
props.align = 'left';
108-
expect(getModifiers(props)).toHaveLength(2);
106+
expect(getMiddleware(props)).toHaveLength(0);
109107

110108
props.align = 'right';
111-
expect(getModifiers(props)).toHaveLength(2);
109+
expect(getMiddleware(props)).toHaveLength(0);
110+
111+
props.align = 'justify';
112+
expect(getMiddleware(props)).toHaveLength(1);
112113
});
113114
});

src/components/Overlay/__snapshots__/Overlay.test.tsx.snap

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@ exports[`<Overlay> Default story renders snapshot 1`] = `
1010
<div
1111
aria-label="menu-options"
1212
class="rbt-menu dropdown-menu show"
13-
data-popper-escaped="true"
14-
data-popper-placement="bottom-start"
15-
data-popper-reference-hidden="true"
1613
id="overlay-story"
1714
role="listbox"
18-
style="position: fixed; left: 0px; top: 0px; display: block; max-height: 300px; overflow: auto; transform: translate(0px, 0px); width: 0px;"
15+
style="position: fixed; left: 0px; top: 0px; display: block; max-height: 300px; overflow: auto; transform: translate(0px, 0px);"
1916
>
2017
<a
2118
class="dropdown-item"

src/components/Overlay/useOverlay.ts

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
import type { ModifierArguments, Placement, Options } from '@popperjs/core';
2-
import { useEffect, useState } from 'react';
3-
import { usePopper } from 'react-popper';
1+
import {
2+
autoUpdate,
3+
flip,
4+
Middleware,
5+
Placement,
6+
size,
7+
useFloating,
8+
} from '@floating-ui/react-dom';
9+
import { useState } from 'react';
410

511
import { Align } from '../../types';
612

7-
const setPopperWidth = {
8-
enabled: true,
9-
fn: (data: ModifierArguments<Options>) => {
10-
// eslint-disable-next-line no-param-reassign
11-
data.state.styles.popper.width = `${data.state.rects.reference.width}px`;
12-
},
13-
name: 'setPopperWidth',
14-
phase: 'write',
15-
};
16-
1713
export type ReferenceElement = HTMLElement | null;
1814

1915
export interface OverlayOptions {
@@ -23,25 +19,25 @@ export interface OverlayOptions {
2319
positionFixed?: boolean;
2420
}
2521

26-
export function getModifiers(props: Pick<OverlayOptions, 'align' | 'flip'>) {
27-
const modifiers = [
28-
{
29-
enabled: !!props.flip,
30-
name: 'flip',
31-
},
32-
{
33-
name: 'preventOverflow',
34-
options: {
35-
mainAxis: false,
36-
},
37-
},
38-
];
22+
export function getMiddleware(props: Pick<OverlayOptions, 'align' | 'flip'>) {
23+
const middleware: Middleware[] = [];
24+
if (props.flip) {
25+
middleware.push(flip());
26+
}
3927

4028
if (props.align !== 'right' && props.align !== 'left') {
41-
modifiers.push(setPopperWidth);
29+
middleware.push(
30+
size({
31+
apply({ rects, elements }) {
32+
Object.assign(elements.floating.style, {
33+
width: `${rects.reference.width}px`,
34+
});
35+
},
36+
})
37+
);
4238
}
4339

44-
return modifiers;
40+
return middleware;
4541
}
4642

4743
export function getPlacement(
@@ -57,29 +53,21 @@ export function useOverlay(
5753
referenceElement: ReferenceElement,
5854
options: OverlayOptions
5955
) {
60-
const [popperElement, attachRef] = useState<ReferenceElement>(null);
61-
const { attributes, styles, forceUpdate } = usePopper(
62-
referenceElement,
63-
popperElement,
64-
{
65-
modifiers: getModifiers(options),
66-
placement: getPlacement(options),
67-
strategy: options.positionFixed ? 'fixed' : 'absolute',
68-
}
69-
);
70-
71-
const refElementHeight = referenceElement?.offsetHeight;
72-
73-
// Re-position the popper if the height of the reference element changes.
74-
// Exclude `forceUpdate` from dependencies since it changes with each render.
75-
useEffect(() => {
76-
forceUpdate && forceUpdate();
77-
}, [refElementHeight]); // eslint-disable-line
56+
const [floatingElement, attachRef] = useState<ReferenceElement>(null);
57+
const { floatingStyles } = useFloating({
58+
elements: {
59+
floating: floatingElement,
60+
reference: referenceElement,
61+
},
62+
middleware: getMiddleware(options),
63+
placement: getPlacement(options),
64+
strategy: options.positionFixed ? 'fixed' : 'absolute',
65+
whileElementsMounted: autoUpdate,
66+
});
7867

7968
return {
80-
...attributes.popper,
8169
innerRef: attachRef,
82-
style: styles.popper,
70+
style: floatingStyles,
8371
};
8472
}
8573

yarn.lock

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,33 @@
13591359
minimatch "^3.0.4"
13601360
strip-json-comments "^3.1.1"
13611361

1362+
"@floating-ui/core@^1.6.0":
1363+
version "1.6.9"
1364+
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.9.tgz#64d1da251433019dafa091de9b2886ff35ec14e6"
1365+
integrity sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==
1366+
dependencies:
1367+
"@floating-ui/utils" "^0.2.9"
1368+
1369+
"@floating-ui/dom@^1.0.0":
1370+
version "1.6.13"
1371+
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34"
1372+
integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==
1373+
dependencies:
1374+
"@floating-ui/core" "^1.6.0"
1375+
"@floating-ui/utils" "^0.2.9"
1376+
1377+
"@floating-ui/react-dom@^2.1.2":
1378+
version "2.1.2"
1379+
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
1380+
integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==
1381+
dependencies:
1382+
"@floating-ui/dom" "^1.0.0"
1383+
1384+
"@floating-ui/utils@^0.2.9":
1385+
version "0.2.9"
1386+
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429"
1387+
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
1388+
13621389
"@gar/promisify@^1.0.1":
13631390
version "1.1.3"
13641391
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
@@ -1780,7 +1807,7 @@
17801807
schema-utils "^3.0.0"
17811808
source-map "^0.7.3"
17821809

1783-
"@popperjs/core@^2.10.2", "@popperjs/core@^2.11.6":
1810+
"@popperjs/core@^2.11.6":
17841811
version "2.11.8"
17851812
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
17861813
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
@@ -10969,11 +10996,6 @@ react-element-to-jsx-string@^14.3.4:
1096910996
is-plain-object "5.0.0"
1097010997
react-is "17.0.2"
1097110998

10972-
react-fast-compare@^3.0.1:
10973-
version "3.2.2"
10974-
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
10975-
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
10976-
1097710999
react-inspector@^5.1.0:
1097811000
version "5.1.1"
1097911001
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
@@ -11017,14 +11039,6 @@ react-overlays@^5.2.0:
1101711039
uncontrollable "^7.2.1"
1101811040
warning "^4.0.3"
1101911041

11020-
react-popper@^2.2.5:
11021-
version "2.3.0"
11022-
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
11023-
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
11024-
dependencies:
11025-
react-fast-compare "^3.0.1"
11026-
warning "^4.0.2"
11027-
1102811042
react-refresh@^0.11.0:
1102911043
version "0.11.0"
1103011044
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
@@ -12133,7 +12147,16 @@ string-length@^4.0.1:
1213312147
char-regex "^1.0.2"
1213412148
strip-ansi "^6.0.0"
1213512149

12136-
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
12150+
"string-width-cjs@npm:string-width@^4.2.0":
12151+
version "4.2.3"
12152+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
12153+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
12154+
dependencies:
12155+
emoji-regex "^8.0.0"
12156+
is-fullwidth-code-point "^3.0.0"
12157+
strip-ansi "^6.0.1"
12158+
12159+
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
1213712160
version "4.2.3"
1213812161
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1213912162
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12239,7 +12262,7 @@ string_decoder@~1.1.1:
1223912262
dependencies:
1224012263
safe-buffer "~5.1.0"
1224112264

12242-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
12265+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
1224312266
version "6.0.1"
1224412267
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1224512268
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -12253,6 +12276,13 @@ strip-ansi@^3.0.1:
1225312276
dependencies:
1225412277
ansi-regex "^2.0.0"
1225512278

12279+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
12280+
version "6.0.1"
12281+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
12282+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
12283+
dependencies:
12284+
ansi-regex "^5.0.1"
12285+
1225612286
strip-ansi@^7.0.1:
1225712287
version "7.1.0"
1225812288
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -13169,7 +13199,7 @@ walker@^1.0.7, walker@^1.0.8, walker@~1.0.5:
1316913199
dependencies:
1317013200
makeerror "1.0.12"
1317113201

13172-
warning@^4.0.1, warning@^4.0.2, warning@^4.0.3:
13202+
warning@^4.0.1, warning@^4.0.3:
1317313203
version "4.0.3"
1317413204
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
1317513205
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
@@ -13458,7 +13488,16 @@ worker-rpc@^0.1.0:
1345813488
dependencies:
1345913489
microevent.ts "~0.1.1"
1346013490

13461-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
13491+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
13492+
version "7.0.0"
13493+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
13494+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
13495+
dependencies:
13496+
ansi-styles "^4.0.0"
13497+
string-width "^4.1.0"
13498+
strip-ansi "^6.0.0"
13499+
13500+
wrap-ansi@^7.0.0:
1346213501
version "7.0.0"
1346313502
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
1346413503
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

0 commit comments

Comments
 (0)