diff --git a/public/app/features/commandPalette/CommandPalette.tsx b/public/app/features/commandPalette/CommandPalette.tsx index b70709e443e..7ee00813631 100644 --- a/public/app/features/commandPalette/CommandPalette.tsx +++ b/public/app/features/commandPalette/CommandPalette.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { useDialog } from '@react-aria/dialog'; import { FocusScope } from '@react-aria/focus'; import { useOverlay } from '@react-aria/overlays'; @@ -83,24 +83,33 @@ const RenderResults = ({ dashboardResults }: RenderResultsProps) => { () => dashboardResults.map((dashboard) => new ActionImpl(dashboard, { store: {} })), [dashboardResults] ); + const items = useMemo( () => (dashboardResultItems.length > 0 ? [...results, dashboardsSectionTitle, ...dashboardResultItems] : results), [results, dashboardsSectionTitle, dashboardResultItems] ); return ( -
- + { + // These items are rendered in a container, in a virtual list, so we cannot + // use :first/last-child selectors, so we must mimic them in JS + const isFirstItem = items[0] === item; + const isLastItem = items[items.length - 1] === item; + + const renderedItem = typeof item === 'string' ? ( -
{item}
+
+
{item}
+
) : ( - ) - } - /> -
+ ); + + return isLastItem ?
{renderedItem}
: renderedItem; + }} + /> ); }; @@ -137,15 +146,30 @@ const getSearchStyles = (theme: GrafanaTheme2) => ({ border: 'none', background: theme.colors.background.canvas, color: theme.colors.text.primary, - borderBottom: `1px solid ${theme.colors.border.weak}`, + borderBottom: `1px solid ${theme.colors.border.medium}`, }), + + // Virtual list measures margin incorrectly, so we need to split padding before/after border + // over and inner and outer element sectionHeader: css({ - padding: theme.spacing(1, 2), + paddingTop: theme.spacing(2), fontSize: theme.typography.h6.fontSize, fontWeight: theme.typography.body.fontWeight, color: theme.colors.text.secondary, }), - resultsContainer: css({ - padding: theme.spacing(2, 0), + sectionHeaderInner: css({ + padding: theme.spacing(1, 2), + borderTop: `1px solid ${theme.colors.border.medium}`, + }), + + // We don't need the header above the first section + sectionHeaderInnerFirst: css({ + borderTop: 'none', + paddingTop: 0, + }), + + // Last item gets extra padding so it's not clipped by the rounded corners on the container + lastItem: css({ + paddingBottom: theme.spacing(1), }), }); diff --git a/public/app/features/commandPalette/ResultItem.tsx b/public/app/features/commandPalette/ResultItem.tsx index d82be2c2c89..fc4858fa00e 100644 --- a/public/app/features/commandPalette/ResultItem.tsx +++ b/public/app/features/commandPalette/ResultItem.tsx @@ -78,6 +78,7 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { alightItems: 'center', justifyContent: 'space-between', cursor: 'pointer', + position: 'relative', '&:before': { display: isActive ? 'block' : 'none', content: '" "', @@ -107,8 +108,8 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { fontSize: theme.typography.fontSize, }), breadcrumbAncestor: css({ - opacity: 0.5, marginRight: theme.spacing(1), + color: theme.colors.text.secondary, }), subtitleText: css({ fontSize: theme.typography.fontSize - 2,