mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
Navigation: Open command palette from search box (#61667)
* Reduce size of topnav search 'input' to 1/5th of the width, min width 200px * Open command palette on topnav search box click * Rename component * fix comment * feature flag the change * update feature flag description
This commit is contained in:
parent
ec171bcad5
commit
88347caf5f
@ -46,6 +46,7 @@ Some stable features are enabled by default. You can disable a stable feature by
|
||||
| `datasourceLogger` | Logs all datasource requests |
|
||||
| `accessControlOnCall` | Access control primitives for OnCall |
|
||||
| `alertingNoNormalState` | Stop maintaining state of alerts that are not firing |
|
||||
| `topNavCommandPalette` | Launch the Command Palette from the top navigation search box |
|
||||
|
||||
## Alpha feature toggles
|
||||
|
||||
|
@ -90,4 +90,5 @@ export interface FeatureToggles {
|
||||
editPanelCSVDragAndDrop?: boolean;
|
||||
alertingNoNormalState?: boolean;
|
||||
azureMultipleResourcePicker?: boolean;
|
||||
topNavCommandPalette?: boolean;
|
||||
}
|
||||
|
@ -414,5 +414,11 @@ var (
|
||||
Description: "Azure multiple resource picker",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "topNavCommandPalette",
|
||||
Description: "Launch the Command Palette from the top navigation search box",
|
||||
State: FeatureStateBeta,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -302,4 +302,8 @@ const (
|
||||
// FlagAzureMultipleResourcePicker
|
||||
// Azure multiple resource picker
|
||||
FlagAzureMultipleResourcePicker = "azureMultipleResourcePicker"
|
||||
|
||||
// FlagTopNavCommandPalette
|
||||
// Launch the Command Palette from the top navigation search box
|
||||
FlagTopNavCommandPalette = "topNavCommandPalette"
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Dropdown, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
@ -14,6 +15,7 @@ import { QuickAdd } from './QuickAdd/QuickAdd';
|
||||
import { SignInLink } from './TopBar/SignInLink';
|
||||
import { TopNavBarMenu } from './TopBar/TopNavBarMenu';
|
||||
import { TopSearchBarSection } from './TopBar/TopSearchBarSection';
|
||||
import { TopSearchBarCommandPaletteTrigger } from './TopSearchBarCommandPaletteTrigger';
|
||||
import { TopSearchBarInput } from './TopSearchBarInput';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
||||
|
||||
@ -24,6 +26,13 @@ export function TopSearchBar() {
|
||||
const helpNode = navIndex['help'];
|
||||
const profileNode = navIndex['profile'];
|
||||
|
||||
const search =
|
||||
config.featureToggles.commandPalette && config.featureToggles.topNavcommandPalette ? (
|
||||
<TopSearchBarCommandPaletteTrigger />
|
||||
) : (
|
||||
<TopSearchBarInput />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<TopSearchBarSection>
|
||||
@ -32,9 +41,9 @@ export function TopSearchBar() {
|
||||
</a>
|
||||
<OrganizationSwitcher />
|
||||
</TopSearchBarSection>
|
||||
<TopSearchBarSection>
|
||||
<TopSearchBarInput />
|
||||
</TopSearchBarSection>
|
||||
|
||||
<TopSearchBarSection>{search}</TopSearchBarSection>
|
||||
|
||||
<TopSearchBarSection align="right">
|
||||
<QuickAdd />
|
||||
{helpNode && (
|
||||
@ -70,7 +79,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
justifyContent: 'space-between',
|
||||
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
gridTemplateColumns: '1fr 1fr 1fr',
|
||||
gridTemplateColumns: '2fr minmax(200px, 1fr) 2fr', // search should not be smaller than 200px
|
||||
display: 'grid',
|
||||
|
||||
justifyContent: 'flex-start',
|
||||
|
@ -0,0 +1,109 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useKBar, VisualState } from 'kbar';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { getInputStyles, Icon, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { focusCss } from '@grafana/ui/src/themes/mixins';
|
||||
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
export function TopSearchBarCommandPaletteTrigger() {
|
||||
const theme = useTheme2();
|
||||
const { query: kbar } = useKBar((kbarState) => ({
|
||||
kbarSearchQuery: kbarState.searchQuery,
|
||||
kbarIsOpen: kbarState.visualState === VisualState.showing,
|
||||
}));
|
||||
|
||||
const breakpoint = theme.breakpoints.values.sm;
|
||||
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(window.matchMedia(`(max-width: ${breakpoint}px)`).matches);
|
||||
|
||||
useMediaQueryChange({
|
||||
breakpoint,
|
||||
onChange: (e) => {
|
||||
setIsSmallScreen(e.matches);
|
||||
},
|
||||
});
|
||||
|
||||
const onOpenSearch = () => {
|
||||
kbar.toggle();
|
||||
};
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconOnly
|
||||
icon="search"
|
||||
aria-label={t('nav.search.placeholder', 'Search Grafana')}
|
||||
onClick={onOpenSearch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <PretendTextInput onClick={onOpenSearch} />;
|
||||
}
|
||||
|
||||
function PretendTextInput({ onClick }: { onClick: () => void }) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// We want the desktop command palette trigger to look like a search box,
|
||||
// but it actually behaves like a button - you active it and it performs an
|
||||
// action. You don't actually type into it.
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.prefix}>
|
||||
<Icon name="search" />
|
||||
</div>
|
||||
|
||||
<button className={styles.fakeInput} onClick={onClick}>
|
||||
{t('nav.search.placeholder', 'Search Grafana')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const baseStyles = getInputStyles({ theme });
|
||||
|
||||
return {
|
||||
wrapper: baseStyles.wrapper,
|
||||
inputWrapper: baseStyles.inputWrapper,
|
||||
prefix: baseStyles.prefix,
|
||||
fakeInput: css([
|
||||
baseStyles.input,
|
||||
{
|
||||
textAlign: 'left',
|
||||
paddingLeft: 28,
|
||||
color: theme.colors.text.disabled,
|
||||
|
||||
// We want the focus styles to appear only when tabbing through, not when clicking the button
|
||||
// (and when focus is restored after command palette closes)
|
||||
'&:focus': {
|
||||
outline: 'unset',
|
||||
boxShadow: 'unset',
|
||||
},
|
||||
|
||||
'&:focus-visible': css`
|
||||
${focusCss(theme)}
|
||||
`,
|
||||
},
|
||||
]),
|
||||
|
||||
button: css({
|
||||
// height: 32,
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
|
||||
'> *': {
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1ch',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user