diff --git a/public/app/features/scopes/internal/ScopesSelectorScene.tsx b/public/app/features/scopes/internal/ScopesSelectorScene.tsx index e02ef57e17b..be63f15ac77 100644 --- a/public/app/features/scopes/internal/ScopesSelectorScene.tsx +++ b/public/app/features/scopes/internal/ScopesSelectorScene.tsx @@ -351,39 +351,44 @@ export function ScopesSelectorSceneRenderer({ model }: SceneComponentProps - {isLoadingScopes ? ( - - ) : ( - model.updateNode(path, isExpanded, query)} - onNodeSelectToggle={(path) => model.toggleNodeSelect(path)} - /> - )} -
- - +
+
+ {isLoadingScopes ? ( + + ) : ( + model.updateNode(path, isExpanded, query)} + onNodeSelectToggle={(path) => model.toggleNodeSelect(path)} + /> + )} +
+ +
+ + +
)} @@ -410,7 +415,20 @@ const getStyles = (theme: GrafanaTheme2, menuDockedAndOpen: boolean) => { color: theme.colors.text.primary, }), }), - buttonGroup: css({ + drawerContainer: css({ + display: 'flex', + flexDirection: 'column', + height: '100%', + }), + treeContainer: css({ + display: 'flex', + flexDirection: 'column', + maxHeight: '100%', + overflowY: 'hidden', + // Fix for top level search outline overflow due to scrollbars + paddingLeft: theme.spacing(0.5), + }), + buttonsContainer: css({ display: 'flex', gap: theme.spacing(1), marginTop: theme.spacing(8), diff --git a/public/app/features/scopes/internal/ScopesTree.tsx b/public/app/features/scopes/internal/ScopesTree.tsx index 6f97a7e9b83..d4116f6ba1e 100644 --- a/public/app/features/scopes/internal/ScopesTree.tsx +++ b/public/app/features/scopes/internal/ScopesTree.tsx @@ -1,11 +1,11 @@ -import { groupBy } from 'lodash'; +import { Dictionary, groupBy } from 'lodash'; import { useMemo } from 'react'; import { ScopesTreeHeadline } from './ScopesTreeHeadline'; import { ScopesTreeItem } from './ScopesTreeItem'; import { ScopesTreeLoading } from './ScopesTreeLoading'; import { ScopesTreeSearch } from './ScopesTreeSearch'; -import { NodeReason, NodesMap, OnNodeSelectToggle, OnNodeUpdate, TreeScope } from './types'; +import { Node, NodeReason, NodesMap, OnNodeSelectToggle, OnNodeUpdate, TreeScope } from './types'; export interface ScopesTreeProps { nodes: NodesMap; @@ -30,7 +30,8 @@ export function ScopesTree({ const isNodeLoading = loadingNodeName === nodeId; const scopeNames = scopes.map(({ scopeName }) => scopeName); const anyChildExpanded = childNodes.some(({ isExpanded }) => isExpanded); - const groupedNodes = useMemo(() => groupBy(childNodes, 'reason'), [childNodes]); + const groupedNodes: Dictionary = useMemo(() => groupBy(childNodes, 'reason'), [childNodes]); + const isLastExpandedNode = !anyChildExpanded && node.isExpanded; return ( <> @@ -44,11 +45,12 @@ export function ScopesTree({ ; + isLastExpandedNode: boolean; loadingNodeName: string | undefined; node: Node; nodePath: string[]; - nodes: Node[]; + nodeReason: NodeReason; scopeNames: string[]; scopes: TreeScope[]; type: 'persisted' | 'result'; @@ -23,10 +25,12 @@ export interface ScopesTreeItemProps { export function ScopesTreeItem({ anyChildExpanded, + groupedNodes, + isLastExpandedNode, loadingNodeName, node, nodePath, - nodes, + nodeReason, scopeNames, scopes, type, @@ -35,8 +39,14 @@ export function ScopesTreeItem({ }: ScopesTreeItemProps) { const styles = useStyles2(getStyles); - return ( -
+ const nodes = groupedNodes[nodeReason] || []; + + if (nodes.length === 0) { + return null; + } + + const children = ( +
{nodes.map((childNode) => { const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!); @@ -49,8 +59,13 @@ export function ScopesTreeItem({ const radioName = childNodePath.join('.'); return ( -
-
+
+
{childNode.isSelectable && !childNode.isExpanded ? ( node.disableMultiSelect ? ( ); + + if (isLastExpandedNode) { + return ( + + {children} + + ); + } + + return children; } const getStyles = (theme: GrafanaTheme2) => { return { + expandedContainer: css({ + display: 'flex', + flexDirection: 'column', + maxHeight: '100%', + }), title: css({ alignItems: 'center', display: 'flex', @@ -127,6 +160,10 @@ const getStyles = (theme: GrafanaTheme2) => { gap: 0, }), }), + titlePadding: css({ + // Fix for checkboxes and radios outline overflow due to scrollbars + paddingLeft: theme.spacing(0.5), + }), expand: css({ alignItems: 'center', background: 'none', @@ -137,6 +174,10 @@ const getStyles = (theme: GrafanaTheme2) => { padding: 0, }), children: css({ + display: 'flex', + flexDirection: 'column', + overflowY: 'hidden', + maxHeight: '100%', paddingLeft: theme.spacing(4), }), }; diff --git a/public/app/features/scopes/internal/ScopesTreeSearch.tsx b/public/app/features/scopes/internal/ScopesTreeSearch.tsx index e0962914f71..180a0d23229 100644 --- a/public/app/features/scopes/internal/ScopesTreeSearch.tsx +++ b/public/app/features/scopes/internal/ScopesTreeSearch.tsx @@ -57,6 +57,10 @@ const getStyles = (theme: GrafanaTheme2) => { return { input: css({ margin: theme.spacing(1, 0), + minHeight: theme.spacing(4), + height: theme.spacing(4), + maxHeight: theme.spacing(4), + width: `calc(100% - ${theme.spacing(0.5)})`, }), }; };