diff --git a/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx index a502f5895b..a22dc77dbe 100644 --- a/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx +++ b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx @@ -1,12 +1,27 @@ import React from "react"; +import Tooltip from '@jetbrains/ring-ui/components/tooltip/tooltip'; import SearchIcon from 'react-svg-loader!../assets/searchIcon.svg'; +import {CustomAnchorProps} from "./types"; +import {Hotkey} from "../utils/hotkey"; + +const HOTKEY_LETTER = 'k' +const HOTKEY_TOOLTIP_DISPLAY_DELAY = 0.5 * 1000 // seconds + +export const DokkaSearchAnchor = ({wrapperProps, buttonProps, popup}: CustomAnchorProps) => { + const hotkeys = new Hotkey() + hotkeys.registerHotkeyWithAccel(buttonProps.onClick, HOTKEY_LETTER) -export const DokkaSearchAnchor = ({wrapperProps, buttonProps, popup}: any) => { return ( - + + + {popup} ) diff --git a/plugins/base/frontend/src/main/components/search/search.scss b/plugins/base/frontend/src/main/components/search/search.scss index 24ad509fb5..780ea6b3bd 100644 --- a/plugins/base/frontend/src/main/components/search/search.scss +++ b/plugins/base/frontend/src/main/components/search/search.scss @@ -18,6 +18,11 @@ $secondary-font-color: hsla(0, 0%, 100%, 0.6); } } +.search-hotkey-popup{ + background-color: var(--background-color) !important; + padding: 4px; +} + .popup-wrapper { min-width: calc(100% - 322px) !important; diff --git a/plugins/base/frontend/src/main/components/search/search.tsx b/plugins/base/frontend/src/main/components/search/search.tsx index dfdcd462f2..d4e406bfd7 100644 --- a/plugins/base/frontend/src/main/components/search/search.tsx +++ b/plugins/base/frontend/src/main/components/search/search.tsx @@ -3,7 +3,7 @@ import List from '@jetbrains/ring-ui/components/list/list'; import Select from '@jetbrains/ring-ui/components/select/select'; import '@jetbrains/ring-ui/components/input-size/input-size.css'; import './search.scss'; -import { IWindow, Option, Props } from "./types"; +import {CustomAnchorProps, IWindow, Option, Props} from "./types"; import { DokkaSearchAnchor } from "./dokkaSearchAnchor"; import { DokkaFuzzyFilterComponent } from "./dokkaFuzzyFilter"; import { relativizeUrlForRequest } from '../utils/requests'; @@ -34,7 +34,7 @@ const WithFuzzySearchFilterComponent: React.FC = ({ data }: Props) => { data={data} popupClassName={"popup-wrapper"} onSelect={onChangeSelected} - customAnchor={({ wrapperProps, buttonProps, popup }) => + customAnchor={({ wrapperProps, buttonProps, popup }: CustomAnchorProps) => } /> diff --git a/plugins/base/frontend/src/main/components/search/types.ts b/plugins/base/frontend/src/main/components/search/types.ts index 84e5539937..57e7e16952 100644 --- a/plugins/base/frontend/src/main/components/search/types.ts +++ b/plugins/base/frontend/src/main/components/search/types.ts @@ -1,4 +1,4 @@ -import React from "react"; +import React, {ButtonHTMLAttributes, HTMLAttributes, ReactNode, RefCallback} from "react"; export type Page = { name: string; @@ -37,3 +37,15 @@ export type OptionWithHighlightComponent = Option & { export type SearchProps = { searchResult: OptionWithSearchResult, } + +export interface DataTestProps { + 'data-test'?: string | null | undefined +} + +export interface CustomAnchorProps { + wrapperProps: HTMLAttributes & DataTestProps & {ref: RefCallback} + buttonProps: Pick, 'id' | 'disabled' | 'children'> & + {onClick: () => void} & + DataTestProps, + popup: ReactNode +} \ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/utils/hotkey.ts b/plugins/base/frontend/src/main/components/utils/hotkey.ts new file mode 100644 index 0000000000..8ba47ab5b6 --- /dev/null +++ b/plugins/base/frontend/src/main/components/utils/hotkey.ts @@ -0,0 +1,58 @@ +import {detectOsKind, OsKind} from "./os"; + +type ModifierKey = { + name: string + keyArg: string +} + +class ModifierKeys { + static metaKey: ModifierKey = {name: "Command", keyArg: "Meta"} + static ctrlKey: ModifierKey = {name: "Ctrl", keyArg: "Control"} + static altKey: ModifierKey = {name: "Alt", keyArg: "Alt"} + static shiftKey: ModifierKey = {name: "Shift", keyArg: "Shift"} +} + +const setOfKeys = [ModifierKeys.altKey, ModifierKeys.shiftKey, ModifierKeys.ctrlKey, ModifierKeys.metaKey] + +export class Hotkey { + private readonly osKind: OsKind; + + constructor() { + this.osKind = detectOsKind() + } + + public getOsAccelKeyName() { + return this.getOsAccelKey().name + } + + /** + * Register a hotkey of combination Accel key (Cmd/Ctrl depending on OS). + * The method also checks that other modifiers key is not pressed to avoid shortcuts intersection. + * E.g. don't trigger [Ctrl+K] if [Ctrl + Shift + K] pressed + */ + public registerHotkeyWithAccel = (event: () => void, letter: string) => { + const osMetaKey = this.getOsAccelKey() + document.onkeydown = (keyDownEvent) => { + const isMetaKeyPressed = keyDownEvent.getModifierState(osMetaKey.keyArg) + const isOtherModifierKeyPressed = setOfKeys + .filter(key => key !== osMetaKey) + .map((otherKeys: ModifierKey) => keyDownEvent.getModifierState(otherKeys.keyArg)) + .some(value => value) + + if (isMetaKeyPressed && !isOtherModifierKeyPressed && keyDownEvent.key === letter) { + keyDownEvent.preventDefault() + event() + } + }; + } + + private getOsAccelKey(): ModifierKey { + switch (this.osKind) { + case OsKind.MACOS: + return ModifierKeys.metaKey + default: + return ModifierKeys.ctrlKey + } + } +} + diff --git a/plugins/base/frontend/src/main/components/utils/os.ts b/plugins/base/frontend/src/main/components/utils/os.ts new file mode 100644 index 0000000000..3005245c32 --- /dev/null +++ b/plugins/base/frontend/src/main/components/utils/os.ts @@ -0,0 +1,14 @@ +export enum OsKind{ + WINDOWS, + MACOS, + LINUX, + OTHER +} + +export const detectOsKind = (): OsKind => { + const userAgent = navigator.userAgent + if(userAgent.includes("Mac")) return OsKind.MACOS + else if (userAgent.includes("Win")) return OsKind.WINDOWS + else if (userAgent.includes("Linux")) return OsKind.LINUX + else return OsKind.OTHER +} \ No newline at end of file diff --git a/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts b/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts index 3d2096574e..a1e9f1c13c 100644 --- a/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts +++ b/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts @@ -1,4 +1,5 @@ declare module '@jetbrains/ring-ui' { + export const Tooltip: any; export const Select: any; export const List: any; }