Skip to content

Commit 56e578b

Browse files
committed
Replace popper with floating-ui
This has the nice benefit of better accessibility, hooray!
1 parent 724d4ba commit 56e578b

File tree

6 files changed

+100
-99
lines changed

6 files changed

+100
-99
lines changed

ui/frontend/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = {
6262
{
6363
files: [
6464
'.eslintrc.js',
65+
'PopButton.tsx',
6566
'editor/AceEditor.tsx',
6667
'editor/SimpleEditor.tsx',
6768
'websocketMiddleware.ts',

ui/frontend/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ node_modules
1111

1212
# Slowly migrate files that we've touched
1313
!.eslintrc.js
14+
!PopButton.tsx
1415
!editor/AceEditor.tsx
1516
!editor/SimpleEditor.tsx
1617
!websocketMiddleware.ts

ui/frontend/PopButton.module.css

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
$fg-color: #222;
22
$bg-color: white;
3-
$arrow-size: 10px;
43
$vertical-border-color: #cacaca;
4+
$arrow-height: 10px;
5+
$arrow-width: 20px;
56

67
.container {
7-
/* Prevents the popper from shifting when adding it to the DOM
8-
* triggers showing the scrollbars.
9-
* https://github.com/FezVrasta/popper.js/issues/457#issuecomment-367692177
10-
*/
11-
top: 0;
128
z-index: 10;
139
font-size: 12px;
1410

@@ -17,18 +13,8 @@ $vertical-border-color: #cacaca;
1713
}
1814
}
1915

20-
.arrow {
21-
border: $arrow-size solid transparent;
22-
}
23-
24-
.container[data-popper-placement^='bottom'] .arrow {
25-
margin-top: 0;
26-
border-top-width: 0;
27-
border-bottom-color: $bg-color;
28-
}
29-
3016
.content {
31-
margin: $arrow-size;
17+
margin: 0 $arrow-height $arrow-height $arrow-height;
3218
box-shadow: 0 1px 4px -2px rgb(0 0 0 / 60%), inset 0 1px 0 white;
3319
border-right: 1px solid $vertical-border-color;
3420
border-bottom: 1px solid var(--header-accent-border);

ui/frontend/PopButton.tsx

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,74 @@
1-
import React, { useCallback, useState, useEffect } from 'react';
2-
import { usePopper } from 'react-popper';
3-
import { Portal } from 'react-portal';
1+
import {
2+
FloatingArrow,
3+
FloatingFocusManager,
4+
arrow,
5+
autoUpdate,
6+
flip,
7+
offset,
8+
shift,
9+
useClick,
10+
useDismiss,
11+
useFloating,
12+
useInteractions,
13+
useRole,
14+
} from '@floating-ui/react';
15+
import React, { useCallback, useRef, useState } from 'react';
416

517
import styles from './PopButton.module.css';
618

719
interface NewPopProps {
8-
Button: React.ComponentType<{
9-
toggle: () => void;
10-
} & React.RefAttributes<HTMLButtonElement>>;
20+
Button: React.ComponentType<
21+
{
22+
toggle: () => void;
23+
} & React.RefAttributes<HTMLButtonElement>
24+
>;
1125
Menu: React.ComponentType<{ close: () => void }>;
1226
}
1327

1428
const PopButton: React.FC<NewPopProps> = ({ Button, Menu }) => {
15-
const [isOpen, setOpen] = useState(false);
16-
const toggle = useCallback(() => setOpen(v => !v), []);
17-
const close = useCallback(() => setOpen(false), []);
29+
const [isOpen, setIsOpen] = useState(false);
30+
const toggle = useCallback(() => setIsOpen((v) => !v), []);
31+
const close = useCallback(() => setIsOpen(false), []);
1832

19-
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null);
20-
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
21-
const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);
33+
const arrowRef = useRef(null);
2234

23-
const { styles: popperStyles, attributes: popperAttributes } = usePopper(referenceElement, popperElement, {
24-
modifiers: [
25-
{ name: 'arrow', options: { element: arrowElement } },
26-
// Issue #303
27-
{ name: 'computeStyles', options: { gpuAcceleration: false } },
28-
],
35+
const { x, y, refs, strategy, context } = useFloating({
36+
open: isOpen,
37+
onOpenChange: setIsOpen,
38+
middleware: [offset(10), flip(), shift(), arrow({ element: arrowRef })],
39+
whileElementsMounted: autoUpdate,
2940
});
3041

31-
useEffect(() => {
32-
if (!isOpen) {
33-
return;
34-
}
42+
const click = useClick(context);
43+
const dismiss = useDismiss(context);
44+
const role = useRole(context);
3545

36-
const handleClickOutside = (event: MouseEvent) => {
37-
if (!(event.target instanceof Node)) { return; }
38-
39-
if (referenceElement && referenceElement.contains(event.target)) {
40-
// They are clicking on the button, so let that go ahead and close us.
41-
return;
42-
}
43-
44-
if (popperElement && !popperElement.contains(event.target)) {
45-
close();
46-
}
47-
};
48-
49-
document.addEventListener('mousedown', handleClickOutside);
50-
return () => document.removeEventListener('mousedown', handleClickOutside);
51-
}, [isOpen, referenceElement, popperElement, close]);
46+
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
5247

5348
return (
5449
<>
55-
<Button ref={setReferenceElement} toggle={toggle} />
50+
<Button toggle={toggle} ref={refs.setReference} {...getReferenceProps()} />
5651

57-
{isOpen && <Portal>
58-
<div
59-
ref={setPopperElement}
60-
className={styles.container}
61-
style={popperStyles.popper}
62-
{...popperAttributes.popper}>
52+
{isOpen && (
53+
<FloatingFocusManager context={context}>
6354
<div
64-
ref={setArrowElement}
65-
className={styles.arrow}
66-
style={popperStyles.arrow}
67-
{...popperAttributes.arrow} />
68-
<div className={styles.content}>
69-
<Menu close={close} />
55+
ref={refs.setFloating}
56+
className={styles.container}
57+
style={{
58+
position: strategy,
59+
top: y ?? 0,
60+
left: x ?? 0,
61+
width: 'max-content',
62+
}}
63+
{...getFloatingProps()}
64+
>
65+
<FloatingArrow ref={arrowRef} context={context} height={10} width={20} fill="white" />
66+
<div className={styles.content}>
67+
<Menu close={close} />
68+
</div>
7069
</div>
71-
</div>
72-
</Portal>}
70+
</FloatingFocusManager>
71+
)}
7372
</>
7473
);
7574
};

ui/frontend/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "UI for the Rust playground",
55
"main": "index.js",
66
"dependencies": {
7-
"@popperjs/core": "^2.4.0",
7+
"@floating-ui/react": "^0.22.2",
88
"ace-builds": "^1.4.4",
99
"common-tags": "^1.8.0",
1010
"core-js": "^3.1.3",
@@ -18,7 +18,6 @@
1818
"react-copy-to-clipboard": "^5.0.1",
1919
"react-dom": "^18.2.0",
2020
"react-monaco-editor": "^0.52.0",
21-
"react-popper": "^2.0.0",
2221
"react-portal": "^4.1.4",
2322
"react-prism": "^4.0.0",
2423
"react-redux": "^8.0.2",

ui/frontend/yarn.lock

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,34 @@
10831083
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
10841084
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==
10851085

1086+
"@floating-ui/core@^1.2.4":
1087+
version "1.2.5"
1088+
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.5.tgz#612f0d203e6f647490d572c7b798eebac9e3cf54"
1089+
integrity sha512-qrcbyfnRVziRlB6IYwjCopYhO7Vud750JlJyuljruIXcPxr22y8zdckcJGsuOdnQ639uVD1tTXddrcH3t3QYIQ==
1090+
1091+
"@floating-ui/dom@^1.2.1":
1092+
version "1.2.5"
1093+
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.5.tgz#c9ec259a24ce0958b1ea29674df4eee4455361a9"
1094+
integrity sha512-+sAUfpQ3Frz+VCbPCqj+cZzvEESy3fjSeT/pDWkYCWOBXYNNKZfuVsHuv8/JO2zze8+Eb/Q7a6hZVgzS81fLbQ==
1095+
dependencies:
1096+
"@floating-ui/core" "^1.2.4"
1097+
1098+
"@floating-ui/react-dom@^1.3.0":
1099+
version "1.3.0"
1100+
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
1101+
integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==
1102+
dependencies:
1103+
"@floating-ui/dom" "^1.2.1"
1104+
1105+
"@floating-ui/react@^0.22.2":
1106+
version "0.22.2"
1107+
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.22.2.tgz#57d3e32dd82790be5dfcf1a42a44b4a3d0366ca1"
1108+
integrity sha512-7u5JqNfcbUCY9WNGJvcbaoChTx5fbFlW2Mpo/6B5DzB+pPWRBbFknALRUTcXj599Sm7vCZ2HdJS9hID22QKriQ==
1109+
dependencies:
1110+
"@floating-ui/react-dom" "^1.3.0"
1111+
aria-hidden "^1.1.3"
1112+
tabbable "^6.0.1"
1113+
10861114
"@humanwhocodes/config-array@^0.11.8":
10871115
version "0.11.8"
10881116
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
@@ -1379,11 +1407,6 @@
13791407
"@nodelib/fs.scandir" "2.1.5"
13801408
fastq "^1.6.0"
13811409

1382-
"@popperjs/core@^2.4.0":
1383-
version "2.11.7"
1384-
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
1385-
integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==
1386-
13871410
"@sinclair/typebox@^0.25.16":
13881411
version "0.25.24"
13891412
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
@@ -2014,6 +2037,13 @@ argparse@^2.0.1:
20142037
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
20152038
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
20162039

2040+
aria-hidden@^1.1.3:
2041+
version "1.2.3"
2042+
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954"
2043+
integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==
2044+
dependencies:
2045+
tslib "^2.0.0"
2046+
20172047
array-buffer-byte-length@^1.0.0:
20182048
version "1.0.0"
20192049
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
@@ -4425,7 +4455,7 @@ lodash@^4.17.20, lodash@^4.17.21:
44254455
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
44264456
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
44274457

4428-
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
4458+
loose-envify@^1.1.0, loose-envify@^1.4.0:
44294459
version "1.4.0"
44304460
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
44314461
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -5188,11 +5218,6 @@ react-dom@^18.2.0:
51885218
loose-envify "^1.1.0"
51895219
scheduler "^0.23.0"
51905220

5191-
react-fast-compare@^3.0.1:
5192-
version "3.2.1"
5193-
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz#53933d9e14f364281d6cba24bfed7a4afb808b5f"
5194-
integrity sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==
5195-
51965221
react-is@^16.13.1, react-is@^16.7.0:
51975222
version "16.13.1"
51985223
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -5210,14 +5235,6 @@ react-monaco-editor@^0.52.0:
52105235
dependencies:
52115236
prop-types "^15.8.1"
52125237

5213-
react-popper@^2.0.0:
5214-
version "2.3.0"
5215-
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
5216-
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
5217-
dependencies:
5218-
react-fast-compare "^3.0.1"
5219-
warning "^4.0.2"
5220-
52215238
react-portal@^4.1.4:
52225239
version "4.2.2"
52235240
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.2.tgz#bff1e024147d6041ba8c530ffc99d4c8248f49fa"
@@ -6007,6 +6024,11 @@ sync-threads@^1.0.1:
60076024
resolved "https://registry.yarnpkg.com/sync-threads/-/sync-threads-1.0.1.tgz#1e854ce579eaca0d0f1f0885a40bc2be6237b593"
60086025
integrity sha512-hIdwt/c/e1ONnr2RJmfBxEAj/J6KQQWKdToF3Qw8ZNRsTNNteGkOe63rQy9I7J5UNlr8Yl0wkzIr9wgLY94x0Q==
60096026

6027+
tabbable@^6.0.1:
6028+
version "6.1.1"
6029+
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.1.1.tgz#40cfead5ed11be49043f04436ef924c8890186a0"
6030+
integrity sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==
6031+
60106032
table@^6.8.1:
60116033
version "6.8.1"
60126034
resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
@@ -6143,7 +6165,7 @@ tslib@^1.8.1:
61436165
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
61446166
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
61456167

6146-
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
6168+
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
61476169
version "2.5.0"
61486170
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
61496171
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
@@ -6320,13 +6342,6 @@ walker@^1.0.8:
63206342
dependencies:
63216343
makeerror "1.0.12"
63226344

6323-
warning@^4.0.2:
6324-
version "4.0.3"
6325-
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
6326-
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
6327-
dependencies:
6328-
loose-envify "^1.0.0"
6329-
63306345
watchpack@^2.4.0:
63316346
version "2.4.0"
63326347
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"

0 commit comments

Comments
 (0)