From 2b25bbf997e5169722d461811e2e5063defaf2ce Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Thu, 6 Nov 2025 10:12:46 +0100 Subject: [PATCH] UI: Speed up alerts/rules/... pages by not rendering collapsed content In contrast to Bootstrap, Mantine's Accordion component didn't remove its panel contents from the DOM when collapsed, so rendering pages with lots of collapsed Accordion items was way slower and more resource-intensive in the new Mantine UI. While I talked to Vitaly from Mantine and he managed to add unmounting of collapsed panel contents in Mantine 9, this will only be available next year. So for now, I'm forking over the Accordion component from Mantine and adding a hacky modification to it that removes contents for collapsed panels. This fork can be removed after upgrading to Mantine 9 sometime in 2026. I removed all the unnecessary test files and so on and just kept the core Accordion code files. This should really help with the following issues: https://github.com/prometheus/prometheus/issues/17254 https://github.com/prometheus/prometheus/issues/16830 The /alerts and /rules pages should be the most affected since the panels on those are collapsed by default. The /targets and /service-discovery pages have expanded panels by default, but I still swapped out the Accordion implementation for consistency and in case someone collapses a bunch of panels. Signed-off-by: Julius Volz --- NOTICE | 5 + web/ui/mantine-ui/eslint.config.mjs | 2 +- web/ui/mantine-ui/src/README-PROMETHEUS.md | 10 + .../components/Accordion/Accordion.context.ts | 56 ++++ .../components/Accordion/Accordion.module.css | 204 +++++++++++++ .../src/components/Accordion/Accordion.tsx | 280 ++++++++++++++++++ .../components/Accordion/Accordion.types.ts | 35 +++ .../components/Accordion/AccordionChevron.tsx | 66 +++++ .../AccordionControl/AccordionControl.tsx | 175 +++++++++++ .../Accordion/AccordionItem.context.ts | 39 +++ .../Accordion/AccordionItem/AccordionItem.tsx | 84 ++++++ .../AccordionPanel/AccordionPanel.tsx | 106 +++++++ .../src/components/Accordion/index.ts | 47 +++ web/ui/mantine-ui/src/pages/AlertsPage.tsx | 2 +- web/ui/mantine-ui/src/pages/RulesPage.tsx | 2 +- .../ServiceDiscoveryPoolsList.tsx | 2 +- .../src/pages/targets/ScrapePoolsList.tsx | 7 +- 17 files changed, 1113 insertions(+), 9 deletions(-) create mode 100644 web/ui/mantine-ui/src/README-PROMETHEUS.md create mode 100644 web/ui/mantine-ui/src/components/Accordion/Accordion.context.ts create mode 100644 web/ui/mantine-ui/src/components/Accordion/Accordion.module.css create mode 100644 web/ui/mantine-ui/src/components/Accordion/Accordion.tsx create mode 100644 web/ui/mantine-ui/src/components/Accordion/Accordion.types.ts create mode 100644 web/ui/mantine-ui/src/components/Accordion/AccordionChevron.tsx create mode 100644 web/ui/mantine-ui/src/components/Accordion/AccordionControl/AccordionControl.tsx create mode 100644 web/ui/mantine-ui/src/components/Accordion/AccordionItem.context.ts create mode 100644 web/ui/mantine-ui/src/components/Accordion/AccordionItem/AccordionItem.tsx create mode 100644 web/ui/mantine-ui/src/components/Accordion/AccordionPanel/AccordionPanel.tsx create mode 100644 web/ui/mantine-ui/src/components/Accordion/index.ts diff --git a/NOTICE b/NOTICE index 8605c258e3..b2fcf4deb4 100644 --- a/NOTICE +++ b/NOTICE @@ -101,6 +101,11 @@ https://github.com/microsoft/vscode-codicons Copyright (c) Microsoft Corporation and other contributors See https://github.com/microsoft/vscode-codicons/blob/main/LICENSE for license details. +Mantine UI +https://github.com/mantinedev/mantine +Copyright (c) 2021 Vitaly Rtishchev +See https://github.com/mantinedev/mantine/blob/master/LICENSE for license details. + We also use code from a large number of npm packages. For details, see: - https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/package.json - https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/package-lock.json diff --git a/web/ui/mantine-ui/eslint.config.mjs b/web/ui/mantine-ui/eslint.config.mjs index c3cc58920e..413560417d 100644 --- a/web/ui/mantine-ui/eslint.config.mjs +++ b/web/ui/mantine-ui/eslint.config.mjs @@ -16,7 +16,7 @@ const compat = new FlatCompat({ }); export default [{ - ignores: ['**/dist', '**/.eslintrc.cjs'], + ignores: ['**/dist', '**/.eslintrc.cjs', 'src/components/Accordion/**'], }, ...fixupConfigRules(compat.extends( 'eslint:recommended', 'plugin:@typescript-eslint/recommended', diff --git a/web/ui/mantine-ui/src/README-PROMETHEUS.md b/web/ui/mantine-ui/src/README-PROMETHEUS.md new file mode 100644 index 0000000000..6fd2b4814f --- /dev/null +++ b/web/ui/mantine-ui/src/README-PROMETHEUS.md @@ -0,0 +1,10 @@ +This is a temporary fork of the Accordion component from Mantine UI v8.3.6 with modifications specific to Prometheus. +The component has been modified to unmount children of collapsed panels to reduce page rendering times and +resource usage. + +According to Mantine author Vitaly, a similar feature has now been added to Mantine itself, but will only be +available in version 9.0.0 and later, quote from https://discord.com/channels/854810300876062770/1006447791498870784/threads/1428787320546525336: + +> I've managed to implement it, but only in 9.0 since it requires some breaking changes. Will be available next year + +So this Accordion fork can be removed once Prometheus upgrades to Mantine v9 or later. diff --git a/web/ui/mantine-ui/src/components/Accordion/Accordion.context.ts b/web/ui/mantine-ui/src/components/Accordion/Accordion.context.ts new file mode 100644 index 0000000000..63844ab80b --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/Accordion.context.ts @@ -0,0 +1,56 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { createSafeContext, GetStylesApi } from "@mantine/core"; +import type { AccordionFactory } from "./Accordion"; +import { + AccordionChevronPosition, + AccordionHeadingOrder, +} from "./Accordion.types"; + +interface AccordionContext { + loop: boolean | undefined; + transitionDuration: number | undefined; + disableChevronRotation: boolean | undefined; + chevronPosition: AccordionChevronPosition | undefined; + order: AccordionHeadingOrder | undefined; + chevron: React.ReactNode; + onChange: (value: string) => void; + isItemActive: (value: string) => boolean; + getControlId: (value: string) => string; + getRegionId: (value: string) => string; + getStyles: GetStylesApi; + variant: string | undefined; + unstyled: boolean | undefined; +} + +export const [AccordionProvider, useAccordionContext] = + createSafeContext( + "Accordion component was not found in the tree" + ); diff --git a/web/ui/mantine-ui/src/components/Accordion/Accordion.module.css b/web/ui/mantine-ui/src/components/Accordion/Accordion.module.css new file mode 100644 index 0000000000..9bf3c6a3e0 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/Accordion.module.css @@ -0,0 +1,204 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +.root { + --accordion-radius: var(--mantine-radius-default); +} + +.panel { + overflow-wrap: break-word; +} + +.content { + padding: var(--mantine-spacing-md); + padding-top: calc(var(--mantine-spacing-xs) / 2); +} + +.itemTitle { + margin: 0; + padding: 0; +} + +.control { + width: 100%; + display: flex; + align-items: center; + flex-direction: row-reverse; + padding-inline: var(--mantine-spacing-md); + opacity: 1; + cursor: pointer; + background-color: transparent; + color: var(--mantine-color-bright); + + &:where([data-chevron-position="left"]) { + flex-direction: row; + padding-inline-start: 0; + } + + &:where(:disabled, [data-disabled]) { + opacity: 0.4; + cursor: not-allowed; + } +} + +.control--default, +.control--contained { + &:where(:not(:disabled, [data-disabled])) { + @mixin hover { + @mixin where-light { + background-color: var(--mantine-color-gray-0); + } + + @mixin where-dark { + background-color: var(--mantine-color-dark-6); + } + } + } +} + +.label { + color: inherit; + font-weight: 400; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + padding-top: var(--mantine-spacing-sm); + padding-bottom: var(--mantine-spacing-sm); +} + +.chevron { + display: flex; + align-items: center; + justify-content: flex-start; + transition: transform var(--accordion-transition-duration, 200ms) ease; + width: var(--accordion-chevron-size, rem(15px)); + min-width: var(--accordion-chevron-size, rem(15px)); + transform: rotate(0deg); + + &:where([data-rotate]) { + transform: rotate(180deg); + } + + &:where([data-position="left"]) { + margin-inline-end: var(--mantine-spacing-md); + margin-inline-start: var(--mantine-spacing-md); + } +} + +.icon { + display: flex; + align-items: center; + justify-content: center; + margin-inline-end: var(--mantine-spacing-sm); + + &:where([data-chevron-position="left"]) { + margin-inline-end: 0; + margin-inline-start: var(--mantine-spacing-lg); + } +} + +.item { + @mixin where-light { + --item-border-color: var(--mantine-color-gray-3); + --item-filled-color: var(--mantine-color-gray-0); + } + + @mixin where-dark { + --item-border-color: var(--mantine-color-dark-4); + --item-filled-color: var(--mantine-color-dark-6); + } +} + +.item--default { + border-bottom: 1px solid var(--item-border-color); +} + +.item--contained { + border: 1px solid var(--item-border-color); + transition: background-color 150ms ease; + + &:where([data-active]) { + background-color: var(--item-filled-color); + } + + &:first-of-type { + border-start-start-radius: var(--accordion-radius); + border-start-end-radius: var(--accordion-radius); + + & > [data-accordion-control] { + border-start-start-radius: var(--accordion-radius); + border-start-end-radius: var(--accordion-radius); + } + } + + &:last-of-type { + border-end-start-radius: var(--accordion-radius); + border-end-end-radius: var(--accordion-radius); + + & > [data-accordion-control] { + border-end-start-radius: var(--accordion-radius); + border-end-end-radius: var(--accordion-radius); + } + } + + & + & { + border-top: 0; + } +} + +.item--filled { + border-radius: var(--accordion-radius); + + &:where([data-active]) { + background-color: var(--item-filled-color); + } +} + +.item--separated { + background-color: var(--item-filled-color); + border-radius: var(--accordion-radius); + border: 1px solid transparent; + transition: background-color 150ms ease; + + &[data-active] { + border-color: var(--item-border-color); + + @mixin where-light { + background-color: var(--mantine-color-white); + } + + @mixin where-dark { + background-color: var(--mantine-color-dark-7); + } + } + + & + & { + margin-top: var(--mantine-spacing-md); + } +} diff --git a/web/ui/mantine-ui/src/components/Accordion/Accordion.tsx b/web/ui/mantine-ui/src/components/Accordion/Accordion.tsx new file mode 100644 index 0000000000..220e242352 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/Accordion.tsx @@ -0,0 +1,280 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { useId, useUncontrolled } from "@mantine/hooks"; +import { + Box, + BoxProps, + createVarsResolver, + ElementProps, + ExtendComponent, + Factory, + getRadius, + getSafeId, + getWithProps, + MantineRadius, + MantineThemeComponent, + rem, + StylesApiProps, + useProps, + useStyles, +} from "@mantine/core"; +import { AccordionProvider } from "./Accordion.context"; +import { + AccordionChevronPosition, + AccordionHeadingOrder, + AccordionValue, +} from "./Accordion.types"; +import { AccordionChevron } from "./AccordionChevron"; +import { AccordionControl } from "./AccordionControl/AccordionControl"; +import { AccordionItem } from "./AccordionItem/AccordionItem"; +import { AccordionPanel } from "./AccordionPanel/AccordionPanel"; +import classes from "./Accordion.module.css"; + +export type AccordionStylesNames = + | "root" + | "content" + | "item" + | "panel" + | "icon" + | "chevron" + | "label" + | "itemTitle" + | "control"; + +export type AccordionVariant = "default" | "contained" | "filled" | "separated"; +export type AccordionCssVariables = { + root: + | "--accordion-transition-duration" + | "--accordion-chevron-size" + | "--accordion-radius"; +}; + +export interface AccordionProps + extends BoxProps, + StylesApiProps, + ElementProps<"div", "value" | "defaultValue" | "onChange"> { + /** If set, multiple items can be opened at the same time */ + multiple?: Multiple; + + /** Controlled component value */ + value?: AccordionValue; + + /** Uncontrolled component default value */ + defaultValue?: AccordionValue; + + /** Called when value changes, payload type depends on `multiple` prop */ + onChange?: (value: AccordionValue) => void; + + /** If set, arrow keys loop though items (first to last and last to first) @default `true` */ + loop?: boolean; + + /** Transition duration in ms @default `200` */ + transitionDuration?: number; + + /** If set, chevron rotation is disabled */ + disableChevronRotation?: boolean; + + /** Position of the chevron relative to the item label @default `right` */ + chevronPosition?: AccordionChevronPosition; + + /** Size of the chevron icon container @default `auto` */ + chevronSize?: number | string; + + /** Size of the default chevron icon. Ignored when `chevron` prop is set. @default `16` */ + chevronIconSize?: number | string; + + /** Heading order, has no effect on visuals */ + order?: AccordionHeadingOrder; + + /** Custom chevron icon */ + chevron?: React.ReactNode; + + /** Key of `theme.radius` or any valid CSS value to set border-radius. Numbers are converted to rem. @default `theme.defaultRadius` */ + radius?: MantineRadius; +} + +export type AccordionFactory = Factory<{ + props: AccordionProps; + ref: HTMLDivElement; + stylesNames: AccordionStylesNames; + vars: AccordionCssVariables; + variant: AccordionVariant; +}>; + +const defaultProps = { + multiple: false, + disableChevronRotation: false, + chevronPosition: "right", + variant: "default", + chevronSize: "auto", + chevronIconSize: 16, +} satisfies Partial; + +const varsResolver = createVarsResolver( + (_, { transitionDuration, chevronSize, radius }) => ({ + root: { + "--accordion-transition-duration": + transitionDuration === undefined + ? undefined + : `${transitionDuration}ms`, + "--accordion-chevron-size": + chevronSize === undefined ? undefined : rem(chevronSize), + "--accordion-radius": + radius === undefined ? undefined : getRadius(radius), + }, + }) +); + +export function Accordion( + _props: AccordionProps +) { + const props = useProps( + "Accordion", + defaultProps as AccordionProps, + _props + ); + const { + classNames, + className, + style, + styles, + unstyled, + vars, + children, + multiple, + value, + defaultValue, + onChange, + id, + loop, + transitionDuration, + disableChevronRotation, + chevronPosition, + chevronSize, + order, + chevron, + variant, + radius, + chevronIconSize, + attributes, + ...others + } = props; + + const uid = useId(id); + const [_value, handleChange] = useUncontrolled({ + value, + defaultValue, + finalValue: multiple ? ([] as any) : null, + onChange, + }); + + const isItemActive = (itemValue: string) => + Array.isArray(_value) ? _value.includes(itemValue) : itemValue === _value; + + const handleItemChange = (itemValue: string) => { + const nextValue: AccordionValue = Array.isArray(_value) + ? _value.includes(itemValue) + ? _value.filter((selectedValue) => selectedValue !== itemValue) + : [..._value, itemValue] + : itemValue === _value + ? null + : (itemValue as any); + + handleChange(nextValue); + }; + + const getStyles = useStyles({ + name: "Accordion", + classes, + props: props as AccordionProps, + className, + style, + classNames, + styles, + unstyled, + attributes, + vars, + varsResolver, + }); + + return ( + , + transitionDuration, + disableChevronRotation, + chevronPosition, + order, + loop, + getStyles, + variant, + unstyled, + }} + > + + {children} + + + ); +} + +const extendAccordion = ( + c: ExtendComponent +): MantineThemeComponent => c; + +Accordion.extend = extendAccordion; +Accordion.withProps = getWithProps( + Accordion as any +); +Accordion.classes = classes; +Accordion.displayName = "@mantine/core/Accordion"; +Accordion.Item = AccordionItem; +Accordion.Panel = AccordionPanel; +Accordion.Control = AccordionControl; +Accordion.Chevron = AccordionChevron; diff --git a/web/ui/mantine-ui/src/components/Accordion/Accordion.types.ts b/web/ui/mantine-ui/src/components/Accordion/Accordion.types.ts new file mode 100644 index 0000000000..e197d7ab45 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/Accordion.types.ts @@ -0,0 +1,35 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +export type AccordionValue = Multiple extends true + ? string[] + : string | null; + +export type AccordionHeadingOrder = 2 | 3 | 4 | 5 | 6; +export type AccordionChevronPosition = "left" | "right"; diff --git a/web/ui/mantine-ui/src/components/Accordion/AccordionChevron.tsx b/web/ui/mantine-ui/src/components/Accordion/AccordionChevron.tsx new file mode 100644 index 0000000000..95d746353f --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/AccordionChevron.tsx @@ -0,0 +1,66 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { rem } from "@mantine/core"; + +export interface AccordionChevronProps + extends React.ComponentPropsWithoutRef<"svg"> { + /** Controls `width` and `height` of the icon, `16` by default */ + size?: number | string; +} + +export function AccordionChevron({ + style, + size = 16, + ...others +}: AccordionChevronProps) { + return ( + + + + ); +} + +AccordionChevron.displayName = "@mantine/core/AccordionChevron"; diff --git a/web/ui/mantine-ui/src/components/Accordion/AccordionControl/AccordionControl.tsx b/web/ui/mantine-ui/src/components/Accordion/AccordionControl/AccordionControl.tsx new file mode 100644 index 0000000000..57149d0b2e --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/AccordionControl/AccordionControl.tsx @@ -0,0 +1,175 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { + Box, + BoxProps, + CompoundStylesApiProps, + createScopedKeydownHandler, + ElementProps, + factory, + Factory, + UnstyledButton, + useProps, +} from "@mantine/core"; +import { useAccordionContext } from "../Accordion.context"; +import { useAccordionItemContext } from "../AccordionItem.context"; +import classes from "../Accordion.module.css"; + +export type AccordionControlStylesNames = + | "control" + | "chevron" + | "label" + | "itemTitle" + | "icon"; + +export interface AccordionControlProps + extends BoxProps, + CompoundStylesApiProps, + ElementProps<"button"> { + /** Sets `disabled` attribute, prevents interactions */ + disabled?: boolean; + + /** Custom chevron icon */ + chevron?: React.ReactNode; + + /** Control label */ + children?: React.ReactNode; + + /** Icon displayed next to the label */ + icon?: React.ReactNode; +} + +export type AccordionControlFactory = Factory<{ + props: AccordionControlProps; + ref: HTMLButtonElement; + stylesNames: AccordionControlStylesNames; + compound: true; +}>; + +export const AccordionControl = factory( + (props, ref) => { + const { + classNames, + className, + style, + styles, + vars, + chevron, + icon, + onClick, + onKeyDown, + children, + disabled, + mod, + ...others + } = useProps("AccordionControl", null, props); + + const { value } = useAccordionItemContext(); + const ctx = useAccordionContext(); + const isActive = ctx.isItemActive(value); + const shouldWrapWithHeading = typeof ctx.order === "number"; + const Heading = `h${ctx.order!}` as const; + + const content = ( + + {...others} + {...ctx.getStyles("control", { + className, + classNames, + style, + styles, + variant: ctx.variant, + })} + unstyled={ctx.unstyled} + mod={[ + "accordion-control", + { + active: isActive, + "chevron-position": ctx.chevronPosition, + disabled, + }, + mod, + ]} + ref={ref} + onClick={(event) => { + onClick?.(event); + ctx.onChange(value); + }} + type="button" + disabled={disabled} + aria-expanded={isActive} + aria-controls={ctx.getRegionId(value)} + id={ctx.getControlId(value)} + onKeyDown={createScopedKeydownHandler({ + siblingSelector: "[data-accordion-control]", + parentSelector: "[data-accordion]", + activateOnFocus: false, + loop: ctx.loop, + orientation: "vertical", + onKeyDown, + })} + > + + {chevron || ctx.chevron} + + + {children} + + {icon && ( + + {icon} + + )} + + ); + + return shouldWrapWithHeading ? ( + + {content} + + ) : ( + content + ); + } +); + +AccordionControl.displayName = "@mantine/core/AccordionControl"; +AccordionControl.classes = classes; diff --git a/web/ui/mantine-ui/src/components/Accordion/AccordionItem.context.ts b/web/ui/mantine-ui/src/components/Accordion/AccordionItem.context.ts new file mode 100644 index 0000000000..af8420c070 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/AccordionItem.context.ts @@ -0,0 +1,39 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { createSafeContext } from "@mantine/core"; + +interface AccordionItemContext { + value: string; +} + +export const [AccordionItemProvider, useAccordionItemContext] = + createSafeContext( + "Accordion.Item component was not found in the tree" + ); diff --git a/web/ui/mantine-ui/src/components/Accordion/AccordionItem/AccordionItem.tsx b/web/ui/mantine-ui/src/components/Accordion/AccordionItem/AccordionItem.tsx new file mode 100644 index 0000000000..d0d9da6672 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/AccordionItem/AccordionItem.tsx @@ -0,0 +1,84 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { + Box, + BoxProps, + CompoundStylesApiProps, + ElementProps, + factory, + Factory, + useProps, +} from "@mantine/core"; +import { useAccordionContext } from "../Accordion.context"; +import { AccordionItemProvider } from "../AccordionItem.context"; +import classes from "../Accordion.module.css"; + +export type AccordionItemStylesNames = "item"; + +export interface AccordionItemProps + extends BoxProps, + CompoundStylesApiProps, + ElementProps<"div"> { + /** Value that is used to manage the accordion state */ + value: string; +} + +export type AccordionItemFactory = Factory<{ + props: AccordionItemProps; + ref: HTMLDivElement; + stylesNames: AccordionItemStylesNames; + compound: true; +}>; + +export const AccordionItem = factory((props, ref) => { + const { classNames, className, style, styles, vars, value, mod, ...others } = + useProps("AccordionItem", null, props); + const ctx = useAccordionContext(); + + return ( + + + + ); +}); + +AccordionItem.displayName = "@mantine/core/AccordionItem"; +AccordionItem.classes = classes; diff --git a/web/ui/mantine-ui/src/components/Accordion/AccordionPanel/AccordionPanel.tsx b/web/ui/mantine-ui/src/components/Accordion/AccordionPanel/AccordionPanel.tsx new file mode 100644 index 0000000000..d2c8baa788 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/AccordionPanel/AccordionPanel.tsx @@ -0,0 +1,106 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +import { + BoxProps, + Collapse, + CompoundStylesApiProps, + ElementProps, + factory, + Factory, + useProps, +} from "@mantine/core"; +import { useAccordionContext } from "../Accordion.context"; +import { useAccordionItemContext } from "../AccordionItem.context"; +import classes from "../Accordion.module.css"; +import { useEffect, useState } from "react"; + +export type AccordionPanelStylesNames = "panel" | "content"; + +export interface AccordionPanelProps + extends BoxProps, + CompoundStylesApiProps, + ElementProps<"div"> { + /** Called when the panel animation completes */ + onTransitionEnd?: () => void; +} + +export type AccordionPanelFactory = Factory<{ + props: AccordionPanelProps; + ref: HTMLDivElement; + stylesNames: AccordionPanelStylesNames; + compound: true; +}>; + +export const AccordionPanel = factory((props, ref) => { + const { classNames, className, style, styles, vars, children, ...others } = + useProps("AccordionPanel", null, props); + + const { value } = useAccordionItemContext(); + const ctx = useAccordionContext(); + + const isActive = ctx.isItemActive(value); + + // Prometheus-specific Accordion modification: unmount children when panel is closed. + const [showChildren, setShowChildren] = useState(isActive); + // Hide children from DOM 200ms after collapsing the panel + // to give the animation time to finish. + useEffect(() => { + let timeout: ReturnType; + + if (isActive) { + setShowChildren(true); + } else { + timeout = setTimeout(() => setShowChildren(false), 200); + } + + return () => clearTimeout(timeout); + }, [isActive]); + + return ( + +
+ {/* Prometheus-specific Accordion modification: unmount children when panel is closed. */} + {showChildren && children} +
+
+ ); +}); + +AccordionPanel.displayName = "@mantine/core/AccordionPanel"; +AccordionPanel.classes = classes; diff --git a/web/ui/mantine-ui/src/components/Accordion/index.ts b/web/ui/mantine-ui/src/components/Accordion/index.ts new file mode 100644 index 0000000000..aed1697ed3 --- /dev/null +++ b/web/ui/mantine-ui/src/components/Accordion/index.ts @@ -0,0 +1,47 @@ +/* + * Some parts of this file are derived from Mantine UI (https://github.com/mantinedev/mantine) + * which is distributed under the MIT license: + * + * MIT License + * + * Copyright (c) 2021 Vitaly Rtishchev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Modifications to this file are licensed under the Apache License, Version 2.0. + */ + +export { Accordion } from "./Accordion"; +export { AccordionChevron } from "./AccordionChevron"; +export { AccordionItem } from "./AccordionItem/AccordionItem"; +export { AccordionPanel } from "./AccordionPanel/AccordionPanel"; +export { AccordionControl } from "./AccordionControl/AccordionControl"; + +export type { + AccordionProps, + AccordionStylesNames, + AccordionCssVariables, + AccordionFactory, + AccordionVariant, +} from "./Accordion"; +export type { AccordionControlProps } from "./AccordionControl/AccordionControl"; +export type { AccordionItemProps } from "./AccordionItem/AccordionItem"; +export type { AccordionPanelProps } from "./AccordionPanel/AccordionPanel"; +export type { AccordionChevronProps } from "./AccordionChevron"; +export type { AccordionValue, AccordionHeadingOrder } from "./Accordion.types"; diff --git a/web/ui/mantine-ui/src/pages/AlertsPage.tsx b/web/ui/mantine-ui/src/pages/AlertsPage.tsx index 2608117c8f..ab46077cf8 100644 --- a/web/ui/mantine-ui/src/pages/AlertsPage.tsx +++ b/web/ui/mantine-ui/src/pages/AlertsPage.tsx @@ -3,7 +3,6 @@ import { Group, Table, Text, - Accordion, Badge, Tooltip, Box, @@ -38,6 +37,7 @@ import { KVSearch } from "@nexucis/kvsearch"; import { inputIconStyle } from "../styles"; import CustomInfiniteScroll from "../components/CustomInfiniteScroll"; import classes from "./AlertsPage.module.css"; +import { Accordion } from "../components/Accordion"; type AlertsPageData = { // How many rules are in each state across all groups. diff --git a/web/ui/mantine-ui/src/pages/RulesPage.tsx b/web/ui/mantine-ui/src/pages/RulesPage.tsx index 5f0bc7e5a8..bf95f20d35 100644 --- a/web/ui/mantine-ui/src/pages/RulesPage.tsx +++ b/web/ui/mantine-ui/src/pages/RulesPage.tsx @@ -1,5 +1,4 @@ import { - Accordion, Alert, Anchor, Badge, @@ -46,6 +45,7 @@ import classes from "./RulesPage.module.css"; import { useDebouncedValue, useLocalStorage } from "@mantine/hooks"; import { KVSearch } from "@nexucis/kvsearch"; import { StateMultiSelect } from "../components/StateMultiSelect"; +import { Accordion } from "../components/Accordion"; const kvSearch = new KVSearch({ shouldSort: true, diff --git a/web/ui/mantine-ui/src/pages/service-discovery/ServiceDiscoveryPoolsList.tsx b/web/ui/mantine-ui/src/pages/service-discovery/ServiceDiscoveryPoolsList.tsx index 4ba522d9da..a718646d5f 100644 --- a/web/ui/mantine-ui/src/pages/service-discovery/ServiceDiscoveryPoolsList.tsx +++ b/web/ui/mantine-ui/src/pages/service-discovery/ServiceDiscoveryPoolsList.tsx @@ -1,5 +1,4 @@ import { - Accordion, Alert, Anchor, Box, @@ -33,6 +32,7 @@ import { targetPoolDisplayLimit } from "./ServiceDiscoveryPage"; import { LabelBadges } from "../../components/LabelBadges"; import ErrorBoundary from "../../components/ErrorBoundary"; import RelabelSteps from "./RelabelSteps"; +import { Accordion } from "../../components/Accordion"; type TargetLabels = { discoveredLabels: Labels; diff --git a/web/ui/mantine-ui/src/pages/targets/ScrapePoolsList.tsx b/web/ui/mantine-ui/src/pages/targets/ScrapePoolsList.tsx index fbd710a412..cfbe8b237e 100644 --- a/web/ui/mantine-ui/src/pages/targets/ScrapePoolsList.tsx +++ b/web/ui/mantine-ui/src/pages/targets/ScrapePoolsList.tsx @@ -1,5 +1,4 @@ import { - Accordion, Alert, Anchor, Badge, @@ -10,10 +9,7 @@ import { Text, } from "@mantine/core"; import { KVSearch } from "@nexucis/kvsearch"; -import { - IconAlertTriangle, - IconInfoCircle, -} from "@tabler/icons-react"; +import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; import { useSuspenseAPIQuery } from "../../api/api"; import { Target, TargetsResult } from "../../api/responseTypes/targets"; import React, { FC, memo, useMemo } from "react"; @@ -31,6 +27,7 @@ import panelClasses from "../../Panel.module.css"; import TargetLabels from "./TargetLabels"; import ScrapeTimingDetails from "./ScrapeTimingDetails"; import { targetPoolDisplayLimit } from "./TargetsPage"; +import { Accordion } from "../../components/Accordion"; type ScrapePool = { targets: Target[];