Navigation: Add keyboard shortcut to search input (#62116)

* Show shortcut

* add icon to search bar, make same changes in search modal

* rename to modKey

Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:
Ashley Harrison 2023-01-26 12:07:32 +00:00 committed by GitHub
parent 8f06784449
commit a77c342764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 11 deletions

View File

@ -1,12 +1,13 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useKBar, VisualState } from 'kbar'; import { useKBar, VisualState } from 'kbar';
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { getInputStyles, Icon, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui'; import { getInputStyles, Icon, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui';
import { focusCss } from '@grafana/ui/src/themes/mixins'; import { focusCss } from '@grafana/ui/src/themes/mixins';
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { getModKey } from 'app/core/utils/browser';
export function TopSearchBarCommandPaletteTrigger() { export function TopSearchBarCommandPaletteTrigger() {
const theme = useTheme2(); const theme = useTheme2();
@ -44,8 +45,13 @@ export function TopSearchBarCommandPaletteTrigger() {
return <PretendTextInput onClick={onOpenSearch} />; return <PretendTextInput onClick={onOpenSearch} />;
} }
function PretendTextInput({ onClick }: { onClick: () => void }) { interface PretendTextInputProps {
onClick: () => void;
}
function PretendTextInput({ onClick }: PretendTextInputProps) {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const modKey = useMemo(() => getModKey(), []);
// We want the desktop command palette trigger to look like a search box, // 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 // but it actually behaves like a button - you active it and it performs an
@ -61,6 +67,11 @@ function PretendTextInput({ onClick }: { onClick: () => void }) {
<button className={styles.fakeInput} onClick={onClick}> <button className={styles.fakeInput} onClick={onClick}>
{t('nav.search.placeholder', 'Search Grafana')} {t('nav.search.placeholder', 'Search Grafana')}
</button> </button>
<div className={styles.suffix}>
<Icon name="keyboard" />
<span className={styles.shortcut}>{modKey}+k</span>
</div>
</div> </div>
</div> </div>
); );
@ -73,6 +84,16 @@ const getStyles = (theme: GrafanaTheme2) => {
wrapper: baseStyles.wrapper, wrapper: baseStyles.wrapper,
inputWrapper: baseStyles.inputWrapper, inputWrapper: baseStyles.inputWrapper,
prefix: baseStyles.prefix, prefix: baseStyles.prefix,
suffix: css([
baseStyles.suffix,
{
display: 'flex',
gap: theme.spacing(0.5),
},
]),
shortcut: css({
fontSize: theme.typography.bodySmall.fontSize,
}),
fakeInput: css([ fakeInput: css([
baseStyles.input, baseStyles.input,
{ {

View File

@ -1,10 +1,11 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import React from 'react'; import React, { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Modal, useStyles2 } from '@grafana/ui'; import { Modal, useStyles2 } from '@grafana/ui';
import { getModKey } from 'app/core/utils/browser';
const shortcuts = { const getShortcuts = (modKey: string) => ({
Global: [ Global: [
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' }, { keys: ['g', 'h'], description: 'Go to Home Dashboard' },
{ keys: ['g', 'e'], description: 'Go to Explore' }, { keys: ['g', 'e'], description: 'Go to Explore' },
@ -12,11 +13,11 @@ const shortcuts = {
{ keys: ['s', 'o'], description: 'Open search' }, { keys: ['s', 'o'], description: 'Open search' },
{ keys: ['esc'], description: 'Exit edit/setting views' }, { keys: ['esc'], description: 'Exit edit/setting views' },
{ keys: ['h'], description: 'Show all keyboard shortcuts' }, { keys: ['h'], description: 'Show all keyboard shortcuts' },
{ keys: ['mod+k'], description: 'Open command palette' }, { keys: [`${modKey}+k`], description: 'Open command palette' },
{ keys: ['c', 't'], description: 'Change theme' }, { keys: ['c', 't'], description: 'Change theme' },
], ],
Dashboard: [ Dashboard: [
{ keys: ['mod+s'], description: 'Save dashboard' }, { keys: [`${modKey}+s`], description: 'Save dashboard' },
{ keys: ['d', 'r'], description: 'Refresh all panels' }, { keys: ['d', 'r'], description: 'Refresh all panels' },
{ keys: ['d', 's'], description: 'Dashboard settings' }, { keys: ['d', 's'], description: 'Dashboard settings' },
{ keys: ['d', 'v'], description: 'Toggle in-active / view mode' }, { keys: ['d', 'v'], description: 'Toggle in-active / view mode' },
@ -24,7 +25,7 @@ const shortcuts = {
{ keys: ['d', 'E'], description: 'Expand all rows' }, { keys: ['d', 'E'], description: 'Expand all rows' },
{ keys: ['d', 'C'], description: 'Collapse all rows' }, { keys: ['d', 'C'], description: 'Collapse all rows' },
{ keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' }, { keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' },
{ keys: ['mod+o'], description: 'Toggle shared graph crosshair' }, { keys: [`${modKey}+o`], description: 'Toggle shared graph crosshair' },
{ keys: ['d', 'l'], description: 'Toggle all panel legends' }, { keys: ['d', 'l'], description: 'Toggle all panel legends' },
], ],
'Focused Panel': [ 'Focused Panel': [
@ -50,7 +51,7 @@ const shortcuts = {
description: 'Make time range absolute/permanent', description: 'Make time range absolute/permanent',
}, },
], ],
}; });
export interface HelpModalProps { export interface HelpModalProps {
onDismiss: () => void; onDismiss: () => void;
@ -58,11 +59,10 @@ export interface HelpModalProps {
export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => { export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const modKey = useMemo(() => getModKey(), []);
const shortcuts = useMemo(() => getShortcuts(modKey), [modKey]);
return ( return (
<Modal title="Shortcuts" isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}> <Modal title="Shortcuts" isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}>
<div className={styles.titleDescription}>
<span className={styles.shortcutTableKey}>mod</span> =<span> CTRL on windows or linux and CMD key on Mac</span>
</div>
<div className={styles.categories}> <div className={styles.categories}>
{Object.entries(shortcuts).map(([category, shortcuts], i) => ( {Object.entries(shortcuts).map(([category, shortcuts], i) => (
<div className={styles.shortcutCategory} key={i}> <div className={styles.shortcutCategory} key={i}>

View File

@ -32,3 +32,12 @@ export function checkBrowserCompatibility() {
return true; return true;
} }
export function userAgentIsApple() {
const appleRe = /(iPhone|iPad|Mac)/;
return appleRe.test(navigator.platform);
}
export function getModKey() {
return userAgentIsApple() ? 'cmd' : 'ctrl';
}