Navigation: Command palette topnav tweaks (#61991)

stylings tweaks to command palette
This commit is contained in:
Ashley Harrison
2023-01-26 10:03:16 +00:00
committed by GitHub
parent c4e067d49d
commit a3b396854a
5 changed files with 115 additions and 84 deletions

View File

@@ -79,7 +79,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
justifyContent: 'space-between', justifyContent: 'space-between',
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
gridTemplateColumns: '2fr minmax(200px, 1fr) 2fr', // search should not be smaller than 200px gridTemplateColumns: '1.5fr minmax(200px, 1fr) 1.5fr', // search should not be smaller than 200px
display: 'grid', display: 'grid',
justifyContent: 'flex-start', justifyContent: 'flex-start',

View File

@@ -16,8 +16,8 @@ import {
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { config, reportInteraction } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui'; import { Icon, Spinner, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { ResultItem } from './ResultItem'; import { ResultItem } from './ResultItem';
@@ -34,9 +34,9 @@ export const CommandPalette = () => {
searchQuery: state.searchQuery, searchQuery: state.searchQuery,
})); }));
const actions = useActions(); const actions = useActions(searchQuery);
useRegisterActions(actions, [actions]); useRegisterActions(actions, [actions]);
const dashboardResults = useDashboardResults(searchQuery, showing); const { dashboardResults, isFetchingDashboardResults } = useDashboardResults(searchQuery, showing);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const { overlayProps } = useOverlay( const { overlayProps } = useOverlay(
@@ -56,10 +56,13 @@ export const CommandPalette = () => {
<KBarAnimator className={styles.animator}> <KBarAnimator className={styles.animator}>
<FocusScope contain autoFocus restoreFocus> <FocusScope contain autoFocus restoreFocus>
<div {...overlayProps} {...dialogProps}> <div {...overlayProps} {...dialogProps}>
<KBarSearch <div className={styles.searchContainer}>
defaultPlaceholder={t('command-palette.search-box.placeholder', 'Search or jump to...')} {isFetchingDashboardResults ? <Spinner className={styles.spinner} /> : <Icon name="search" size="md" />}
className={styles.search} <KBarSearch
/> defaultPlaceholder={t('command-palette.search-box.placeholder', 'Search Grafana')}
className={styles.search}
/>
</div>
<div className={styles.resultsContainer}> <div className={styles.resultsContainer}>
<RenderResults dashboardResults={dashboardResults} /> <RenderResults dashboardResults={dashboardResults} />
</div> </div>
@@ -111,55 +114,68 @@ const RenderResults = ({ dashboardResults }: RenderResultsProps) => {
); );
}; };
const getSearchStyles = (theme: GrafanaTheme2) => ({ const getSearchStyles = (theme: GrafanaTheme2) => {
positioner: css({ const topNavCommandPalette = Boolean(config.featureToggles.topNavCommandPalette);
zIndex: theme.zIndex.portal,
marginTop: '0px', return {
'&::before': { positioner: css({
content: '""', zIndex: theme.zIndex.portal,
position: 'fixed', marginTop: '0px',
top: 0, paddingTop: topNavCommandPalette ? '4px !important' : undefined,
right: 0, '&::before': {
bottom: 0, content: '""',
left: 0, position: 'fixed',
background: theme.components.overlay.background, top: 0,
backdropFilter: 'blur(1px)', right: 0,
}, bottom: 0,
}), left: 0,
animator: css({ background: theme.components.overlay.background,
maxWidth: theme.breakpoints.values.md, backdropFilter: 'blur(1px)',
width: '100%', },
background: theme.colors.background.primary, }),
color: theme.colors.text.primary, animator: css({
borderRadius: theme.shape.borderRadius(2), maxWidth: theme.breakpoints.values.md,
border: `1px solid ${theme.colors.border.weak}`, width: '100%',
overflow: 'hidden', background: theme.colors.background.primary,
boxShadow: theme.shadows.z3, color: theme.colors.text.primary,
}), borderRadius: theme.shape.borderRadius(2),
search: css({ border: `1px solid ${theme.colors.border.weak}`,
padding: theme.spacing(1.5, 2), overflow: 'hidden',
fontSize: theme.typography.fontSize, boxShadow: theme.shadows.z3,
width: '100%', }),
boxSizing: 'border-box', searchContainer: css({
outline: 'none', alignItems: 'center',
border: 'none', background: theme.components.input.background,
background: theme.components.input.background, borderBottom: `1px solid ${theme.colors.border.weak}`,
color: theme.components.input.text, display: 'flex',
borderBottom: `1px solid ${theme.colors.border.weak}`, gap: theme.spacing(1),
}), padding: theme.spacing(1, 2),
resultsContainer: css({ }),
paddingBottom: theme.spacing(1), search: css({
}), fontSize: theme.typography.fontSize,
sectionHeader: css({ width: '100%',
padding: theme.spacing(1.5, 2, 1, 2), boxSizing: 'border-box',
fontSize: theme.typography.bodySmall.fontSize, outline: 'none',
fontWeight: theme.typography.fontWeightMedium, border: 'none',
color: theme.colors.text.primary, color: theme.components.input.text,
borderTop: `1px solid ${theme.colors.border.weak}`, }),
marginTop: theme.spacing(1), spinner: css({
}), height: '22px',
sectionHeaderFirst: css({ }),
borderTop: 'none', resultsContainer: css({
marginTop: 0, paddingBottom: theme.spacing(1),
}), }),
}); sectionHeader: css({
padding: theme.spacing(1.5, 2, 1, 2),
fontSize: theme.typography.bodySmall.fontSize,
fontWeight: theme.typography.fontWeightMedium,
color: theme.colors.text.primary,
borderTop: `1px solid ${theme.colors.border.weak}`,
marginTop: theme.spacing(1),
}),
sectionHeaderFirst: css({
borderTop: 'none',
marginTop: 0,
}),
};
};

View File

@@ -34,7 +34,7 @@ export async function getRecentDashboardActions(): Promise<CommandPaletteAction[
const recentDashboardActions: CommandPaletteAction[] = recentResults.map((item) => { const recentDashboardActions: CommandPaletteAction[] = recentResults.map((item) => {
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
return { return {
id: `recent-dashboards/${url}`, id: `recent-dashboards${url}`,
name: `${name}`, name: `${name}`,
section: t('command-palette.section.recent-dashboards', 'Recent dashboards'), section: t('command-palette.section.recent-dashboards', 'Recent dashboards'),
priority: RECENT_DASHBOARDS_PRORITY, priority: RECENT_DASHBOARDS_PRORITY,
@@ -62,7 +62,7 @@ export async function getDashboardSearchResultActions(searchQuery: string): Prom
const goToDashboardActions: CommandPaletteAction[] = data.view.map((item) => { const goToDashboardActions: CommandPaletteAction[] = data.view.map((item) => {
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
return { return {
id: `go/dashboard/${url}`, id: `go/dashboard${url}`,
name: `${name}`, name: `${name}`,
section: t('command-palette.section.dashboard-search-results', 'Dashboards'), section: t('command-palette.section.dashboard-search-results', 'Dashboards'),
priority: SEARCH_RESULTS_PRORITY, priority: SEARCH_RESULTS_PRORITY,
@@ -77,15 +77,23 @@ export async function getDashboardSearchResultActions(searchQuery: string): Prom
export function useDashboardResults(searchQuery: string, isShowing: boolean) { export function useDashboardResults(searchQuery: string, isShowing: boolean) {
const [dashboardResults, setDashboardResults] = useState<CommandPaletteAction[]>([]); const [dashboardResults, setDashboardResults] = useState<CommandPaletteAction[]>([]);
const [isFetchingDashboardResults, setIsFetchingDashboardResults] = useState(false);
// Hit dashboards API // Hit dashboards API
useEffect(() => { useEffect(() => {
if (isShowing && searchQuery.length > 0) { if (isShowing && searchQuery.length > 0) {
setIsFetchingDashboardResults(true);
debouncedDashboardSearch(searchQuery).then((resultActions) => { debouncedDashboardSearch(searchQuery).then((resultActions) => {
setDashboardResults(resultActions); setDashboardResults(resultActions);
setIsFetchingDashboardResults(false);
}); });
} else {
setDashboardResults([]);
} }
}, [isShowing, searchQuery]); }, [isShowing, searchQuery]);
return dashboardResults; return {
dashboardResults,
isFetchingDashboardResults,
};
} }

View File

@@ -1,5 +1,5 @@
import { locationUtil, NavModelItem } from '@grafana/data'; import { locationUtil, NavModelItem } from '@grafana/data';
import { locationService } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { changeTheme } from 'app/core/services/theme'; import { changeTheme } from 'app/core/services/theme';
@@ -49,14 +49,6 @@ function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Comma
export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => { export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => {
const globalActions: CommandPaletteAction[] = [ const globalActions: CommandPaletteAction[] = [
{
id: 'go/search',
name: t('command-palette.action.search', 'Search'),
keywords: 'navigate',
perform: () => locationService.push('?search=open'),
section: t('command-palette.section.pages', 'Pages'),
priority: DEFAULT_PRIORITY,
},
{ {
id: 'preferences/theme', id: 'preferences/theme',
name: t('command-palette.action.change-theme', 'Change theme...'), name: t('command-palette.action.change-theme', 'Change theme...'),
@@ -82,6 +74,17 @@ export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => {
}, },
]; ];
if (!config.featureToggles.topNavCommandPalette) {
globalActions.unshift({
id: 'go/search',
name: t('command-palette.action.search', 'Search'),
keywords: 'navigate',
perform: () => locationService.push('?search=open'),
section: t('command-palette.section.pages', 'Pages'),
priority: DEFAULT_PRIORITY,
});
}
const navBarActions = navTreeToActions(navBarTree); const navBarActions = navTreeToActions(navBarTree);
return [...globalActions, ...navBarActions]; return [...globalActions, ...navBarActions];

View File

@@ -7,29 +7,33 @@ import { CommandPaletteAction } from '../types';
import { getRecentDashboardActions } from './dashboardActions'; import { getRecentDashboardActions } from './dashboardActions';
import getStaticActions from './staticActions'; import getStaticActions from './staticActions';
export default function useActions() { export default function useActions(searchQuery: string) {
const [staticActions, setStaticActions] = useState<CommandPaletteAction[]>([]); const [navTreeActions, setNavTreeActions] = useState<CommandPaletteAction[]>([]);
const [recentDashboardActions, setRecentDashboardActions] = useState<CommandPaletteAction[]>([]);
const { navBarTree } = useSelector((state) => { const { navBarTree } = useSelector((state) => {
return { return {
navBarTree: state.navBarTree, navBarTree: state.navBarTree,
}; };
}); });
// Load standard static actions // Load standard static actions
useEffect(() => { useEffect(() => {
const staticActionsResp = getStaticActions(navBarTree); const staticActionsResp = getStaticActions(navBarTree);
setStaticActions(staticActionsResp); setNavTreeActions(staticActionsResp);
}, [navBarTree]); }, [navBarTree]);
// Load recent dashboards - we don't want them to reload when the nav tree changes // Load recent dashboards - we don't want them to reload when the nav tree changes
useEffect(() => { useEffect(() => {
getRecentDashboardActions() if (!searchQuery) {
.then((recentDashboardActions) => setStaticActions((v) => [...v, ...recentDashboardActions])) getRecentDashboardActions()
.catch((err) => { .then((recentDashboardActions) => setRecentDashboardActions(recentDashboardActions))
console.error('Error loading recent dashboard actions', err); .catch((err) => {
}); console.error('Error loading recent dashboard actions', err);
}, []); });
} else {
setRecentDashboardActions([]);
}
}, [searchQuery]);
return staticActions; return [...recentDashboardActions, ...navTreeActions];
} }