From dcd0152e4bff18be54d003470b66447a1514eff8 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Tue, 11 Nov 2025 16:54:46 +0200 Subject: [PATCH] switch to dropdown --- .../scene/PanelGroupByAction.tsx | 181 ++++++++---------- 1 file changed, 83 insertions(+), 98 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx b/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx index 3a4f859b5db..d4bd11e7a87 100644 --- a/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx +++ b/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx @@ -1,14 +1,4 @@ import { css, cx } from '@emotion/css'; -import { - autoUpdate, - offset, - flip, - shift, - useFloating, - useClick, - useDismiss, - useInteractions, -} from '@floating-ui/react'; import { useState, useCallback, useEffect, useMemo } from 'react'; import { lastValueFrom } from 'rxjs'; @@ -22,7 +12,7 @@ import { VariableValueOption, VizPanel, } from '@grafana/scenes'; -import { Button, Icon, Input, useStyles2, Checkbox, Portal } from '@grafana/ui'; +import { Button, Icon, Input, useStyles2, Checkbox, Dropdown, Stack } from '@grafana/ui'; interface OptionWithChecked extends VariableValueOption { checked: boolean; @@ -110,25 +100,13 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps { setOptions((prevOptions) => prevOptions.map((opt) => (opt.value === item.value ? { ...opt, checked: !opt.checked } : opt)) ); }, []); - const handleApply = () => { + const handleApply = useCallback(() => { const checkedOptions = options.filter((opt) => opt.checked); if (!checkedOptions.length) { @@ -142,12 +120,14 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps { - setIsOpen(false); - }; + const handleVisibilityChange = useCallback((visible: boolean) => { + setIsOpen(visible); + if (!visible) { + setSearchValue(''); + } + }, []); useEffect(() => { const fetchOptions = async () => { @@ -170,7 +150,9 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps { @@ -183,78 +165,84 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps options[idx]); }, [options, searchValue]); + const hasCheckedOptions = useMemo(() => { + return options.some((opt) => opt.checked); + }, [options]); + if (!groupByState || !panelHasGroupBy) { return null; } + const overlayContent = () => ( +
+ e.stopPropagation()} + onKeyDown={(e) => { + if (e.key === 'Tab') { + e.stopPropagation(); + } + }} + > +
+ } + placeholder={t('panel-group-by.search-placeholder', 'Search...')} + value={searchValue} + onChange={(e) => setSearchValue(e.currentTarget.value)} + /> +
+
+ {isLoading ? ( +
+ Loading options... +
+ ) : filteredOptions.length === 0 ? ( +
+ No options found +
+ ) : ( + filteredOptions.map((option) => { + return ( +
handleItemClick(option)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + handleItemClick(option); + } + }} + role="button" + tabIndex={0} + aria-pressed={option.checked} + > + handleItemClick(option)} /> + {option.label} +
+ ); + }) + )} +
+
+
+ + +
+
+ ); + return ( - <> - - - {isOpen && ( - -
- {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */} -
e.stopPropagation()}> - } - placeholder={t('panel-group-by.search-placeholder', 'Search...')} - value={searchValue} - onChange={(e) => setSearchValue(e.currentTarget.value)} - /> -
-
- {isLoading ? ( -
- Loading options... -
- ) : filteredOptions.length === 0 ? ( -
- No options found -
- ) : ( - filteredOptions.map((option) => { - return ( -
{ - e.stopPropagation(); - handleItemClick(option); - }} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - e.stopPropagation(); - handleItemClick(option); - } - }} - role="button" - tabIndex={0} - aria-pressed={option.checked} - > - handleItemClick(option)} /> - {option.label} -
- ); - }) - )} -
-
- - -
-
-
- )} - + ); } @@ -262,13 +250,10 @@ const getStyles = (theme: GrafanaTheme2) => ({ menuContainer: css({ display: 'flex', flexDirection: 'column', - width: '300px', - maxHeight: '400px', background: theme.colors.background.primary, border: `1px solid ${theme.colors.border.weak}`, borderRadius: theme.shape.radius.default, boxShadow: theme.shadows.z3, - zIndex: theme.zIndex.portal, }), searchContainer: css({ padding: theme.spacing(1), @@ -277,7 +262,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ listContainer: css({ flex: 1, overflow: 'auto', - minHeight: '200px', + minHeight: '100px', maxHeight: '300px', padding: theme.spacing(0.5), }),