mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
fix(ui): migrate from popper to floating-ui
This commit is contained in:
committed by
Łukasz Mierzwa
parent
53a79f2e6b
commit
bdfa962731
@@ -6,7 +6,6 @@ ignores:
|
||||
- sass
|
||||
- typeface-open-sans
|
||||
- prettier
|
||||
- "@popperjs/core"
|
||||
- react-scripts
|
||||
- typescript
|
||||
# devDeps
|
||||
|
||||
122
ui/package-lock.json
generated
122
ui/package-lock.json
generated
@@ -9,12 +9,12 @@
|
||||
"version": "0.0.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "0.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@juggle/resize-observer": "3.3.1",
|
||||
"@popperjs/core": "2.11.4",
|
||||
"body-scroll-lock": "3.1.5",
|
||||
"bootstrap": "5.1.3",
|
||||
"bootswatch": "5.1.3",
|
||||
@@ -46,7 +46,6 @@
|
||||
"react-json-pretty": "2.2.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-media-hook": "0.4.9",
|
||||
"react-popper": "2.2.5",
|
||||
"react-range": "1.8.12",
|
||||
"react-select": "5.2.2",
|
||||
"react-transition-group": "4.4.2",
|
||||
@@ -2372,6 +2371,32 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.6.1.tgz",
|
||||
"integrity": "sha512-Y30eVMcZva8o84c0HcXAtDO4BEzPJMvF6+B7x7urL2xbAqVsGJhojOyHLaoQHQYjb6OkqRq5kO+zeySycQwKqg=="
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.4.1.tgz",
|
||||
"integrity": "sha512-VAdIkn+zc4VB3pSemBk2JwjXlbVEBUbQft4V5jGpUeA9Vxd1fQD5eL3dMIMLVW003NYCOoW42JjjyBPNTpr8uQ==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.6.0.tgz",
|
||||
"integrity": "sha512-/dF+jpAUtoonjs2lO0F+miqEQlzA9XJGOxWBiJsq/COhK2kz7XzryyJdxfaNEH+RN63vRs9osDolOJXlst50hg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.4.0",
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.0.tgz",
|
||||
@@ -3658,6 +3683,7 @@
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz",
|
||||
"integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
@@ -26226,11 +26252,6 @@
|
||||
"integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"node_modules/react-hotkeys-hook": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz",
|
||||
@@ -26296,19 +26317,6 @@
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-popper": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz",
|
||||
"integrity": "sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==",
|
||||
"dependencies": {
|
||||
"react-fast-compare": "^3.0.1",
|
||||
"warning": "^4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.0.0",
|
||||
"react": "^16.8.0 || ^17"
|
||||
}
|
||||
},
|
||||
"node_modules/react-range": {
|
||||
"version": "1.8.12",
|
||||
"resolved": "https://registry.npmjs.org/react-range/-/react-range-1.8.12.tgz",
|
||||
@@ -31495,6 +31503,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-isomorphic-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
@@ -31678,14 +31699,6 @@
|
||||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
|
||||
@@ -34604,6 +34617,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.6.1.tgz",
|
||||
"integrity": "sha512-Y30eVMcZva8o84c0HcXAtDO4BEzPJMvF6+B7x7urL2xbAqVsGJhojOyHLaoQHQYjb6OkqRq5kO+zeySycQwKqg=="
|
||||
},
|
||||
"@floating-ui/dom": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.4.1.tgz",
|
||||
"integrity": "sha512-VAdIkn+zc4VB3pSemBk2JwjXlbVEBUbQft4V5jGpUeA9Vxd1fQD5eL3dMIMLVW003NYCOoW42JjjyBPNTpr8uQ==",
|
||||
"requires": {
|
||||
"@floating-ui/core": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@floating-ui/react-dom": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.6.0.tgz",
|
||||
"integrity": "sha512-/dF+jpAUtoonjs2lO0F+miqEQlzA9XJGOxWBiJsq/COhK2kz7XzryyJdxfaNEH+RN63vRs9osDolOJXlst50hg==",
|
||||
"requires": {
|
||||
"@floating-ui/dom": "^0.4.0",
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.0.tgz",
|
||||
@@ -35571,7 +35606,8 @@
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz",
|
||||
"integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg=="
|
||||
"integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==",
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
@@ -53120,11 +53156,6 @@
|
||||
"integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==",
|
||||
"dev": true
|
||||
},
|
||||
"react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"react-hotkeys-hook": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz",
|
||||
@@ -53174,15 +53205,6 @@
|
||||
"integrity": "sha512-FZr/2xA1+23vDJ1IZ794yLqMRRkBoCNOiJATdtTfB5GyVc5djf8FL2qEB/68pSkiNgHdHsmKknMSDr0sC4zBKQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-popper": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz",
|
||||
"integrity": "sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==",
|
||||
"requires": {
|
||||
"react-fast-compare": "^3.0.1",
|
||||
"warning": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"react-range": {
|
||||
"version": "1.8.12",
|
||||
"resolved": "https://registry.npmjs.org/react-range/-/react-range-1.8.12.tgz",
|
||||
@@ -57124,6 +57146,12 @@
|
||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"use-isomorphic-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
@@ -57278,14 +57306,6 @@
|
||||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"node": "17.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "0.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@juggle/resize-observer": "3.3.1",
|
||||
"@popperjs/core": "2.11.4",
|
||||
"body-scroll-lock": "3.1.5",
|
||||
"bootstrap": "5.1.3",
|
||||
"bootswatch": "5.1.3",
|
||||
@@ -45,7 +45,6 @@
|
||||
"react-json-pretty": "2.2.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-media-hook": "0.4.9",
|
||||
"react-popper": "2.2.5",
|
||||
"react-range": "1.8.12",
|
||||
"react-select": "5.2.2",
|
||||
"react-transition-group": "4.4.2",
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export const CommonPopperModifiers = [
|
||||
{ name: "arrow", enabled: false },
|
||||
{
|
||||
name: "computeStyles",
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 5],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "computeStyles",
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
adaptive: false,
|
||||
roundOffsets: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -172,9 +172,10 @@ describe("<AlertMenu />", () => {
|
||||
const MountedMenuContent = (group: APIAlertGroupT) => {
|
||||
return mount(
|
||||
<MenuContent
|
||||
popperPlacement="top"
|
||||
popperRef={jest.fn()}
|
||||
popperStyle={{}}
|
||||
x={0}
|
||||
y={0}
|
||||
floating={null}
|
||||
strategy={"absolute"}
|
||||
group={group}
|
||||
alert={alert}
|
||||
afterClick={MockAfterClick}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FC, Ref, CSSProperties, useRef, useState, useCallback } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
import { useFloating, shift, flip, offset } from "@floating-ui/react-dom";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
|
||||
@@ -21,18 +21,12 @@ import {
|
||||
SilenceFormStore,
|
||||
AlertmanagerClustersToOption,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { FetchPauser } from "Components/FetchPauser";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { DateFromNow } from "Components/DateFromNow";
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
import { MenuLink } from "Components/Grid/AlertGrid/AlertGroup/MenuLink";
|
||||
|
||||
const PopperModifiers = [
|
||||
...CommonPopperModifiers,
|
||||
{ name: "offset", options: { offset: "-5px, 0px" } },
|
||||
];
|
||||
|
||||
const onSilenceClick = (
|
||||
alertStore: AlertStore,
|
||||
silenceFormStore: SilenceFormStore,
|
||||
@@ -60,18 +54,20 @@ const onSilenceClick = (
|
||||
};
|
||||
|
||||
const MenuContent: FC<{
|
||||
popperPlacement?: string;
|
||||
popperRef?: Ref<HTMLDivElement>;
|
||||
popperStyle?: CSSProperties;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
floating: Ref<HTMLDivElement> | null;
|
||||
strategy: CSSProperties["position"];
|
||||
group: APIAlertGroupT;
|
||||
alert: APIAlertT;
|
||||
afterClick: () => void;
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({
|
||||
popperPlacement,
|
||||
popperRef,
|
||||
popperStyle,
|
||||
x,
|
||||
y,
|
||||
floating,
|
||||
strategy,
|
||||
group,
|
||||
alert,
|
||||
afterClick,
|
||||
@@ -91,9 +87,12 @@ const MenuContent: FC<{
|
||||
<FetchPauser alertStore={alertStore}>
|
||||
<div
|
||||
className="dropdown-menu d-block shadow m-0"
|
||||
ref={popperRef}
|
||||
style={popperStyle}
|
||||
data-placement={popperPlacement}
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "",
|
||||
left: x ?? "",
|
||||
}}
|
||||
>
|
||||
<h6 className="dropdown-header">Alert source links:</h6>
|
||||
{alert.alertmanager.map((am) => (
|
||||
@@ -166,43 +165,39 @@ const AlertMenu: FC<{
|
||||
const rootRef = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(rootRef, hide, !isHidden);
|
||||
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: "bottom-start",
|
||||
middleware: [shift(), flip(), offset(5)],
|
||||
});
|
||||
|
||||
return (
|
||||
<span ref={rootRef}>
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
className="components-label components-label-with-hover px-1 me-1 badge bg-secondary cursor-pointer"
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="pe-1"
|
||||
style={{ width: "0.8rem" }}
|
||||
icon={faCaretDown}
|
||||
/>
|
||||
<DateFromNow timestamp={alert.startsAt} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<Popper placement="bottom" modifiers={PopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<MenuContent
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
<span
|
||||
className="components-label components-label-with-hover px-1 me-1 badge bg-secondary cursor-pointer"
|
||||
ref={reference}
|
||||
onClick={toggle}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="pe-1"
|
||||
style={{ width: "0.8rem" }}
|
||||
icon={faCaretDown}
|
||||
/>
|
||||
<DateFromNow timestamp={alert.startsAt} />
|
||||
</span>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<MenuContent
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
x={x}
|
||||
y={y}
|
||||
floating={floating}
|
||||
strategy={strategy}
|
||||
/>
|
||||
</DropdownSlide>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,9 +191,10 @@ describe("<GroupMenu />", () => {
|
||||
const MountedMenuContent = (group: APIAlertGroupT) => {
|
||||
return mount(
|
||||
<MenuContent
|
||||
popperPlacement="top"
|
||||
popperRef={jest.fn()}
|
||||
popperStyle={{}}
|
||||
x={0}
|
||||
y={0}
|
||||
floating={null}
|
||||
strategy={"absolute"}
|
||||
group={group}
|
||||
afterClick={MockAfterClick}
|
||||
alertStore={alertStore}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FC, useRef, useState, useCallback, Ref, CSSProperties } from "react";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
import { useFloating, shift, flip, offset } from "@floating-ui/react-dom";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBars } from "@fortawesome/free-solid-svg-icons/faBars";
|
||||
@@ -18,17 +18,11 @@ import {
|
||||
AlertmanagerClustersToOption,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { FetchPauser } from "Components/FetchPauser";
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
import { MenuLink } from "Components/Grid/AlertGrid/AlertGroup/MenuLink";
|
||||
|
||||
const PopperModifiers = [
|
||||
...CommonPopperModifiers,
|
||||
{ name: "offset", options: { offset: "-5px, 0px" } },
|
||||
];
|
||||
|
||||
const onSilenceClick = (
|
||||
alertStore: AlertStore,
|
||||
silenceFormStore: SilenceFormStore,
|
||||
@@ -56,17 +50,19 @@ const onSilenceClick = (
|
||||
};
|
||||
|
||||
const MenuContent: FC<{
|
||||
popperPlacement?: string;
|
||||
popperRef?: Ref<HTMLDivElement>;
|
||||
popperStyle?: CSSProperties;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
floating: Ref<HTMLDivElement> | null;
|
||||
strategy: CSSProperties["position"];
|
||||
group: APIAlertGroupT;
|
||||
afterClick: () => void;
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({
|
||||
popperPlacement,
|
||||
popperRef,
|
||||
popperStyle,
|
||||
x,
|
||||
y,
|
||||
floating,
|
||||
strategy,
|
||||
group,
|
||||
afterClick,
|
||||
alertStore,
|
||||
@@ -94,9 +90,12 @@ const MenuContent: FC<{
|
||||
<FetchPauser alertStore={alertStore}>
|
||||
<div
|
||||
className="dropdown-menu d-block shadow m-0"
|
||||
ref={popperRef}
|
||||
style={popperStyle}
|
||||
data-placement={popperPlacement}
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "",
|
||||
left: x ?? "",
|
||||
}}
|
||||
>
|
||||
{actions.length ? (
|
||||
<>
|
||||
@@ -165,39 +164,35 @@ const GroupMenu: FC<{
|
||||
const rootRef = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(rootRef, hide, !isHidden);
|
||||
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: "bottom-start",
|
||||
middleware: [shift(), flip(), offset(5)],
|
||||
});
|
||||
|
||||
return (
|
||||
<span ref={rootRef}>
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className={`${
|
||||
themed ? "text-white with-click-light" : "text-muted"
|
||||
} cursor-pointer badge components-label components-label-with-hover with-click me-1`}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon icon={faBars} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<Popper placement="bottom" modifiers={PopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<MenuContent
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
<span
|
||||
ref={reference}
|
||||
onClick={toggle}
|
||||
className={`${
|
||||
themed ? "text-white with-click-light" : "text-muted"
|
||||
} cursor-pointer badge components-label components-label-with-hover with-click me-1`}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon icon={faBars} />
|
||||
</span>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<MenuContent
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
x={x}
|
||||
y={y}
|
||||
floating={floating}
|
||||
strategy={strategy}
|
||||
/>
|
||||
</DropdownSlide>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import React, {
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
import { useFloating, shift, flip, offset } from "@floating-ui/react-dom";
|
||||
|
||||
import type { OnChangeValue } from "react-select";
|
||||
import AsyncSelect from "react-select/async";
|
||||
@@ -20,7 +20,6 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
|
||||
import type { AlertStore } from "Stores/AlertStore";
|
||||
import type { Settings } from "Stores/Settings";
|
||||
import type { APIGridT } from "Models/APITypes";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { StringToOption, OptionT } from "Common/Select";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
@@ -90,17 +89,19 @@ const GridLabelNameSelect: FC<{
|
||||
};
|
||||
|
||||
const Dropdown: FC<{
|
||||
popperPlacement?: string;
|
||||
popperRef?: Ref<HTMLDivElement>;
|
||||
popperStyle?: CSSProperties;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
floating: Ref<HTMLDivElement> | null;
|
||||
strategy: CSSProperties["position"];
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
grid: APIGridT;
|
||||
onClose: () => void;
|
||||
}> = ({
|
||||
popperPlacement,
|
||||
popperRef,
|
||||
popperStyle,
|
||||
x,
|
||||
y,
|
||||
floating,
|
||||
strategy,
|
||||
alertStore,
|
||||
settingsStore,
|
||||
grid,
|
||||
@@ -109,13 +110,14 @@ const Dropdown: FC<{
|
||||
return (
|
||||
<div
|
||||
className="dropdown-menu d-block shadow components-grid-label-select-menu border-0 p-0 m-0"
|
||||
ref={popperRef}
|
||||
ref={floating}
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
fontWeight: "normal",
|
||||
...popperStyle,
|
||||
position: strategy,
|
||||
top: y ?? "",
|
||||
left: x ?? "",
|
||||
}}
|
||||
data-placement={popperPlacement}
|
||||
>
|
||||
<GridLabelNameSelect
|
||||
alertStore={alertStore}
|
||||
@@ -140,37 +142,33 @@ const GridLabelSelect: FC<{
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: "bottom",
|
||||
middleware: [shift(), flip(), offset(5)],
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} className="components-label badge ps-1 pe-2">
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className="border-0 rounded-0 bg-inherit cursor-pointer px-1 py-0 components-grid-label-select-dropdown"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon className="text-muted" icon={faCaretDown} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Popper placement="bottom" modifiers={CommonPopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<Dropdown
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onClose={toggle}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
<span
|
||||
ref={reference}
|
||||
onClick={toggle}
|
||||
className="border-0 rounded-0 bg-inherit cursor-pointer px-1 py-0 components-grid-label-select-dropdown"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon className="text-muted" icon={faCaretDown} />
|
||||
</span>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Dropdown
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onClose={toggle}
|
||||
x={x}
|
||||
y={y}
|
||||
floating={floating}
|
||||
strategy={strategy}
|
||||
/>
|
||||
</DropdownSlide>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -234,7 +234,8 @@ describe("<HistoryMenu />", () => {
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking on 'Save filters' saves current filter set to Settings", () => {
|
||||
it("clicking on 'Save filters' saves current filter set to Settings", async () => {
|
||||
const promise = Promise.resolve();
|
||||
alertStore.filters.setFilterValues([
|
||||
AppliedFilter("foo", "=", "bar"),
|
||||
AppliedFilter("bar", "=~", "baz"),
|
||||
@@ -252,9 +253,11 @@ describe("<HistoryMenu />", () => {
|
||||
expect(settingsStore.savedFilters.config.filters).toHaveLength(2);
|
||||
expect(settingsStore.savedFilters.config.filters).toContain("foo=bar");
|
||||
expect(settingsStore.savedFilters.config.filters).toContain("bar=~baz");
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking on 'Reset filters' clears current filter set in Settings", () => {
|
||||
it("clicking on 'Reset filters' clears current filter set in Settings", async () => {
|
||||
const promise = Promise.resolve();
|
||||
settingsStore.savedFilters.save(["foo=bar"]);
|
||||
const tree = MountedHistory();
|
||||
tree.find("button.cursor-pointer").simulate("click");
|
||||
@@ -266,6 +269,7 @@ describe("<HistoryMenu />", () => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(settingsStore.savedFilters.config.filters).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking on 'Clear history' clears the history", async () => {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import {
|
||||
FC,
|
||||
Ref,
|
||||
CSSProperties,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
ReactNode,
|
||||
CSSProperties,
|
||||
} from "react";
|
||||
|
||||
import { action } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { localStored } from "mobx-stored";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
import { useFloating, shift, flip, offset, size } from "@floating-ui/react-dom";
|
||||
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -26,7 +26,6 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
||||
import type { AlertStore, FilterT } from "Stores/AlertStore";
|
||||
import type { Settings } from "Stores/Settings";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import HistoryLabel from "Components/Labels/HistoryLabel";
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
@@ -69,18 +68,24 @@ const ActionButton: FC<{
|
||||
);
|
||||
|
||||
const HistoryMenu: FC<{
|
||||
popperPlacement?: string;
|
||||
popperRef?: Ref<HTMLDivElement>;
|
||||
popperStyle?: CSSProperties;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
floating: Ref<HTMLDivElement> | null;
|
||||
strategy: CSSProperties["position"];
|
||||
maxWidth: number | null;
|
||||
maxHeight: number | null;
|
||||
filters: ReduceFilterT[][];
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
afterClick: () => void;
|
||||
onClear: () => void;
|
||||
}> = ({
|
||||
popperPlacement,
|
||||
popperRef,
|
||||
popperStyle,
|
||||
x,
|
||||
y,
|
||||
floating,
|
||||
strategy,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
filters,
|
||||
alertStore,
|
||||
settingsStore,
|
||||
@@ -92,9 +97,14 @@ const HistoryMenu: FC<{
|
||||
return (
|
||||
<div
|
||||
className="dropdown-menu d-block shadow components-navbar-historymenu m-0"
|
||||
ref={popperRef}
|
||||
style={popperStyle}
|
||||
data-placement={popperPlacement}
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "",
|
||||
left: x ?? "",
|
||||
maxWidth: maxWidth ?? "",
|
||||
maxHeight: maxHeight ?? "",
|
||||
}}
|
||||
>
|
||||
<h6 className="dropdown-header text-center">
|
||||
<FontAwesomeIcon icon={faHistory} className="me-1" />
|
||||
@@ -185,41 +195,52 @@ const History: FC<{
|
||||
// this will be dumped to local storage via mobx-stored
|
||||
const [history] = useState<HistoryStorage>(new HistoryStorage());
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
const [maxWidth, setMaxWidth] = useState<number | null>(null);
|
||||
const [maxHeight, setMaxHeight] = useState<number | null>(null);
|
||||
const hide = useCallback(() => setIsVisible(false), []);
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
|
||||
const mountRef = useRef<boolean>(false);
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: "bottom-end",
|
||||
middleware: [
|
||||
shift(),
|
||||
flip(),
|
||||
offset(5),
|
||||
size({
|
||||
apply({ width, height }) {
|
||||
setMaxWidth(width);
|
||||
setMaxHeight(height);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// every time this component updates we will rewrite history
|
||||
// (if there are changes)
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
// we don't store unapplied (we only have raw text for those, we need
|
||||
// name & value for coloring) or invalid filters
|
||||
// also check for value, name might be missing for fuzzy filters, but
|
||||
// the value should always be set
|
||||
const validAppliedFilters = alertStore.filters.values
|
||||
.filter((f) => f.applied && f.isValid && f.value)
|
||||
.map((f) => ReduceFilter(f));
|
||||
// we don't store unapplied (we only have raw text for those, we need
|
||||
// name & value for coloring) or invalid filters
|
||||
// also check for value, name might be missing for fuzzy filters, but
|
||||
// the value should always be set
|
||||
const validAppliedFilters = alertStore.filters.values
|
||||
.filter((f) => f.applied && f.isValid && f.value)
|
||||
.map((f) => ReduceFilter(f));
|
||||
|
||||
// don't store empty filters in history
|
||||
if (validAppliedFilters.length === 0) return;
|
||||
// make a JSON dump for comparing later with what's already stored
|
||||
const filtersJSON = JSON.stringify(validAppliedFilters);
|
||||
// don't store empty filters in history
|
||||
if (validAppliedFilters.length === 0) return;
|
||||
// make a JSON dump for comparing later with what's already stored
|
||||
const filtersJSON = JSON.stringify(validAppliedFilters);
|
||||
|
||||
// rewrite history putting current filter set on top, this will move
|
||||
// it up if user selects a filter set that was already in history
|
||||
const newHistory = [
|
||||
...[validAppliedFilters],
|
||||
...history.config.filters.filter(
|
||||
(f) => JSON.stringify(f) !== filtersJSON
|
||||
),
|
||||
].slice(0, 8);
|
||||
history.setFilters(newHistory);
|
||||
} else {
|
||||
mountRef.current = true;
|
||||
}
|
||||
});
|
||||
// rewrite history putting current filter set on top, this will move
|
||||
// it up if user selects a filter set that was already in history
|
||||
const newHistory = [
|
||||
...[validAppliedFilters],
|
||||
...history.config.filters.filter(
|
||||
(f) => JSON.stringify(f) !== filtersJSON
|
||||
),
|
||||
].slice(0, 8);
|
||||
history.setFilters(newHistory);
|
||||
}, [history, alertStore.filters.values]);
|
||||
|
||||
const ref = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
@@ -234,45 +255,34 @@ const History: FC<{
|
||||
ref={ref}
|
||||
className="input-group-text border-0 rounded-0 bg-inherit px-0"
|
||||
>
|
||||
<Manager
|
||||
data-filters={alertStore.filters.values
|
||||
.map((f) => ReduceFilter(f))
|
||||
.join(" ")}
|
||||
<button
|
||||
ref={reference}
|
||||
onClick={toggle}
|
||||
className="btn border-0 rounded-0 bg-inherit cursor-pointer components-navbar-history px-2 py-0 components-navbar-icon"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<button
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className="btn border-0 rounded-0 bg-inherit cursor-pointer components-navbar-history px-2 py-0 components-navbar-icon"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCaretDown} />
|
||||
</button>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Popper placement="bottom" modifiers={CommonPopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<HistoryMenu
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
filters={history.config.filters}
|
||||
onClear={() => {
|
||||
history.setFilters([]);
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
afterClick={hide}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
<FontAwesomeIcon icon={faCaretDown} />
|
||||
</button>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<HistoryMenu
|
||||
filters={history.config.filters}
|
||||
onClear={() => {
|
||||
history.setFilters([]);
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
afterClick={hide}
|
||||
x={x}
|
||||
y={y}
|
||||
floating={floating}
|
||||
strategy={strategy}
|
||||
maxWidth={maxWidth}
|
||||
maxHeight={maxHeight}
|
||||
/>
|
||||
</DropdownSlide>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -172,9 +172,12 @@ storiesOf("NavBar", module).add("NavBar", () => {
|
||||
fixedTop={false}
|
||||
/>
|
||||
<HistoryMenu
|
||||
popperPlacement="top"
|
||||
popperRef={() => {}}
|
||||
popperStyle={{}}
|
||||
x={0}
|
||||
y={0}
|
||||
floating={null}
|
||||
strategy={"absolute"}
|
||||
maxWidth={null}
|
||||
maxHeight={null}
|
||||
filters={history}
|
||||
onClear={() => {}}
|
||||
alertStore={alertStore}
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("TooltipWrapper", () => {
|
||||
expect(tree.find("div.foo").text()).toBe("Hover me");
|
||||
});
|
||||
|
||||
it("on non-touch devices it renders tooltip on mouseOver and hides on mouseLeave", () => {
|
||||
it("on non-touch devices it renders tooltip on mouseOver and hides on mouseLeave", async () => {
|
||||
const tree = mount(
|
||||
<TooltipWrapper title="my title">
|
||||
<span>Hover me</span>
|
||||
@@ -49,9 +49,13 @@ describe("TooltipWrapper", () => {
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.tooltip")).toHaveLength(0);
|
||||
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
|
||||
it("on touch devices it renders tooltip on touchStart and hides on touchEnd", () => {
|
||||
it("on touch devices it renders tooltip on touchStart and hides on touchEnd", async () => {
|
||||
const tree = mount(
|
||||
<TooltipWrapper title="my title">
|
||||
<span>Hover me</span>
|
||||
@@ -77,6 +81,10 @@ describe("TooltipWrapper", () => {
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.tooltip")).toHaveLength(0);
|
||||
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
|
||||
it("hides the tooltip after click and show again on mouseOver", () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createPortal } from "react-dom";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { usePopper } from "react-popper";
|
||||
import { useFloating, shift, flip } from "@floating-ui/react-dom";
|
||||
|
||||
import { useSupportsTouch } from "Hooks/useSupportsTouch";
|
||||
|
||||
@@ -12,20 +12,9 @@ const TooltipWrapper: FC<{
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}> = ({ title, children, className }) => {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(
|
||||
null
|
||||
);
|
||||
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: "top",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
rootBoundary: "viewport",
|
||||
},
|
||||
},
|
||||
],
|
||||
middleware: [shift(), flip()],
|
||||
});
|
||||
|
||||
const supportsTouch = useSupportsTouch();
|
||||
@@ -69,7 +58,7 @@ const TooltipWrapper: FC<{
|
||||
onTouchStart={supportsTouch ? showTooltip : undefined}
|
||||
onTouchCancel={supportsTouch ? hideTooltip : undefined}
|
||||
onTouchEnd={supportsTouch ? hideTooltip : undefined}
|
||||
ref={setReferenceElement}
|
||||
ref={reference}
|
||||
className={`${className ? className : ""} tooltip-trigger`}
|
||||
>
|
||||
{children}
|
||||
@@ -86,9 +75,12 @@ const TooltipWrapper: FC<{
|
||||
>
|
||||
<div
|
||||
className="tooltip tooltip-inner"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "",
|
||||
left: x ?? "",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.dropdown-menu.components-navbar-historymenu {
|
||||
white-space: nowrap;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.dropdown-menu.components-navbar-historymenu > .dropdown-item {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// https://github.com/FezVrasta/popper.js/issues/478#issuecomment-341506071
|
||||
|
||||
export default class Popper {
|
||||
constructor() {
|
||||
return {
|
||||
destroy: () => {},
|
||||
scheduleUpdate: () => {},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ global.console.trace = consoleHandler;
|
||||
FetchRetryConfig.minTimeout = 2;
|
||||
FetchRetryConfig.maxTimeout = 10;
|
||||
|
||||
// usePopper uses useLayoutEffect but that fails in enzyme
|
||||
// floating-ui uses useLayoutEffect but that fails in enzyme
|
||||
React.useLayoutEffect = React.useEffect;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user