From 88347caf5fcc99c21c8a07367b9e846947367636 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 24 Jan 2023 12:41:09 +0000 Subject: [PATCH] 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 --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 6 + pkg/services/featuremgmt/toggles_gen.go | 4 + .../components/AppChrome/TopSearchBar.tsx | 17 ++- .../TopSearchBarCommandPaletteTrigger.tsx | 109 ++++++++++++++++++ 6 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 8fd69ec9ccb..6dccb953693 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -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 diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index f46074443ac..64b10278f96 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -90,4 +90,5 @@ export interface FeatureToggles { editPanelCSVDragAndDrop?: boolean; alertingNoNormalState?: boolean; azureMultipleResourcePicker?: boolean; + topNavCommandPalette?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 1dc085f4b6d..77d111cedf0 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index b2915a9f32a..3ff5c49df92 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" ) diff --git a/public/app/core/components/AppChrome/TopSearchBar.tsx b/public/app/core/components/AppChrome/TopSearchBar.tsx index b5a54d5b9e1..330eede2281 100644 --- a/public/app/core/components/AppChrome/TopSearchBar.tsx +++ b/public/app/core/components/AppChrome/TopSearchBar.tsx @@ -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 ? ( + + ) : ( + + ); + return (
@@ -32,9 +41,9 @@ export function TopSearchBar() { - - - + + {search} + {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', diff --git a/public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx new file mode 100644 index 00000000000..db5b77eecff --- /dev/null +++ b/public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx @@ -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 ( + + ); + } + + return ; +} + +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 ( +
+
+
+ +
+ + +
+
+ ); +} + +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', + }, + }), + }; +};