diff --git a/ui/.depcheckrc.yaml b/ui/.depcheckrc.yaml index ac4bc0e4c..f878d5101 100644 --- a/ui/.depcheckrc.yaml +++ b/ui/.depcheckrc.yaml @@ -6,7 +6,6 @@ ignores: - sass - typeface-open-sans - prettier - - "@popperjs/core" - react-scripts - typescript # devDeps diff --git a/ui/package-lock.json b/ui/package-lock.json index 651ae49f0..87a5dd9fb 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -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", diff --git a/ui/package.json b/ui/package.json index b6bbb1321..a86e88fc7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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", diff --git a/ui/src/Common/Popper.ts b/ui/src/Common/Popper.ts deleted file mode 100644 index 781fbd73f..000000000 --- a/ui/src/Common/Popper.ts +++ /dev/null @@ -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: [], - }, - }, -]; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx index 001b6dd7b..98ccc4a90 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx @@ -172,9 +172,10 @@ describe("", () => { const MountedMenuContent = (group: APIAlertGroupT) => { return mount( ; - popperStyle?: CSSProperties; + x: number | null; + y: number | null; + floating: Ref | 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<{
Alert source links:
{alert.alertmanager.map((am) => ( @@ -166,43 +165,39 @@ const AlertMenu: FC<{ const rootRef = useRef(null); useOnClickOutside(rootRef, hide, !isHidden); + const { x, y, reference, floating, strategy } = useFloating({ + placement: "bottom-start", + middleware: [shift(), flip(), offset(5)], + }); + return ( - - - {({ ref }) => ( - - - - - )} - - - - {({ placement, ref, style }) => ( - - )} - - - + + + + + + + ); } diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx index 4630bebee..891ead84f 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx @@ -191,9 +191,10 @@ describe("", () => { const MountedMenuContent = (group: APIAlertGroupT) => { return mount( ; - popperStyle?: CSSProperties; + x: number | null; + y: number | null; + floating: Ref | 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<{
{actions.length ? ( <> @@ -165,39 +164,35 @@ const GroupMenu: FC<{ const rootRef = useRef(null); useOnClickOutside(rootRef, hide, !isHidden); + const { x, y, reference, floating, strategy } = useFloating({ + placement: "bottom-start", + middleware: [shift(), flip(), offset(5)], + }); + return ( - - - {({ ref }) => ( - - - - )} - - - - {({ placement, ref, style }) => ( - - )} - - - + + + + + + ); }; diff --git a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx index 4975e4bf5..9c4c8136a 100644 --- a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx +++ b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx @@ -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; - popperStyle?: CSSProperties; + x: number | null; + y: number | null; + floating: Ref | 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 (
(null); useOnClickOutside(ref, hide, isVisible); + const { x, y, reference, floating, strategy } = useFloating({ + placement: "bottom", + middleware: [shift(), flip(), offset(5)], + }); + return (
- - - {({ ref }) => ( - - - - )} - - - - {({ placement, ref, style }) => ( - - )} - - - + + + + + +
); }); diff --git a/ui/src/Components/NavBar/FilterInput/History.test.tsx b/ui/src/Components/NavBar/FilterInput/History.test.tsx index 761c78b6f..f14fb9f83 100644 --- a/ui/src/Components/NavBar/FilterInput/History.test.tsx +++ b/ui/src/Components/NavBar/FilterInput/History.test.tsx @@ -234,7 +234,8 @@ describe("", () => { 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("", () => { 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("", () => { jest.runOnlyPendingTimers(); }); expect(settingsStore.savedFilters.config.filters).toHaveLength(0); + await act(() => promise); }); it("clicking on 'Clear history' clears the history", async () => { diff --git a/ui/src/Components/NavBar/FilterInput/History.tsx b/ui/src/Components/NavBar/FilterInput/History.tsx index e893f43bf..83f1b4b52 100644 --- a/ui/src/Components/NavBar/FilterInput/History.tsx +++ b/ui/src/Components/NavBar/FilterInput/History.tsx @@ -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; - popperStyle?: CSSProperties; + x: number | null; + y: number | null; + floating: Ref | 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 (
@@ -185,41 +195,52 @@ const History: FC<{ // this will be dumped to local storage via mobx-stored const [history] = useState(new HistoryStorage()); const [isVisible, setIsVisible] = useState(false); + const [maxWidth, setMaxWidth] = useState(null); + const [maxHeight, setMaxHeight] = useState(null); const hide = useCallback(() => setIsVisible(false), []); const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]); - const mountRef = useRef(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(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" > - ReduceFilter(f)) - .join(" ")} + - )} - - - - {({ placement, ref, style }) => ( - { - history.setFilters([]); - }} - alertStore={alertStore} - settingsStore={settingsStore} - afterClick={hide} - /> - )} - - - + + + + { + history.setFilters([]); + }} + alertStore={alertStore} + settingsStore={settingsStore} + afterClick={hide} + x={x} + y={y} + floating={floating} + strategy={strategy} + maxWidth={maxWidth} + maxHeight={maxHeight} + /> + ); }); diff --git a/ui/src/Components/NavBar/index.stories.tsx b/ui/src/Components/NavBar/index.stories.tsx index 2a83d8e7c..e2909267b 100644 --- a/ui/src/Components/NavBar/index.stories.tsx +++ b/ui/src/Components/NavBar/index.stories.tsx @@ -172,9 +172,12 @@ storiesOf("NavBar", module).add("NavBar", () => { fixedTop={false} /> {}} - popperStyle={{}} + x={0} + y={0} + floating={null} + strategy={"absolute"} + maxWidth={null} + maxHeight={null} filters={history} onClear={() => {}} alertStore={alertStore} diff --git a/ui/src/Components/TooltipWrapper/index.test.tsx b/ui/src/Components/TooltipWrapper/index.test.tsx index ed4fe165d..5f5ff1654 100644 --- a/ui/src/Components/TooltipWrapper/index.test.tsx +++ b/ui/src/Components/TooltipWrapper/index.test.tsx @@ -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( Hover me @@ -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( Hover me @@ -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", () => { diff --git a/ui/src/Components/TooltipWrapper/index.tsx b/ui/src/Components/TooltipWrapper/index.tsx index f473f8ee3..2db7b32f3 100644 --- a/ui/src/Components/TooltipWrapper/index.tsx +++ b/ui/src/Components/TooltipWrapper/index.tsx @@ -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( - null - ); - const [popperElement, setPopperElement] = useState(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<{ >
{title}
diff --git a/ui/src/Styles/Components/_History.scss b/ui/src/Styles/Components/_History.scss index 6aad5348c..ec8142446 100644 --- a/ui/src/Styles/Components/_History.scss +++ b/ui/src/Styles/Components/_History.scss @@ -1,5 +1,6 @@ .dropdown-menu.components-navbar-historymenu { white-space: nowrap; + overflow: scroll; } .dropdown-menu.components-navbar-historymenu > .dropdown-item { diff --git a/ui/src/__mocks__/popper.js.ts b/ui/src/__mocks__/popper.js.ts deleted file mode 100644 index 19b1571bb..000000000 --- a/ui/src/__mocks__/popper.js.ts +++ /dev/null @@ -1,10 +0,0 @@ -// https://github.com/FezVrasta/popper.js/issues/478#issuecomment-341506071 - -export default class Popper { - constructor() { - return { - destroy: () => {}, - scheduleUpdate: () => {}, - }; - } -} diff --git a/ui/src/setupTests.ts b/ui/src/setupTests.ts index 2eced79ee..b828bc3fb 100644 --- a/ui/src/setupTests.ts +++ b/ui/src/setupTests.ts @@ -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(() => {