mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Command palette topnav tweaks (#61991)
stylings tweaks to command palette
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user