diff --git a/packages/docsearch-react/src/DocSearch.tsx b/packages/docsearch-react/src/DocSearch.tsx index d6d8474ff..c4d5d503e 100644 --- a/packages/docsearch-react/src/DocSearch.tsx +++ b/packages/docsearch-react/src/DocSearch.tsx @@ -41,6 +41,7 @@ export interface DocSearchProps { initialQuery?: string; navigator?: AutocompleteOptions['navigator']; translations?: DocSearchTranslations; + toggleKey?: string; } export function DocSearch(props: DocSearchProps) { @@ -49,6 +50,7 @@ export function DocSearch(props: DocSearchProps) { const [initialQuery, setInitialQuery] = React.useState( props?.initialQuery || undefined ); + const toggleKey = props?.toggleKey || 'k'; const onOpen = React.useCallback(() => { setIsOpen(true); @@ -72,6 +74,7 @@ export function DocSearch(props: DocSearchProps) { onClose, onInput, searchButtonRef, + toggleKey, }); return ( @@ -79,6 +82,7 @@ export function DocSearch(props: DocSearchProps) { diff --git a/packages/docsearch-react/src/DocSearchButton.tsx b/packages/docsearch-react/src/DocSearchButton.tsx index fd6acbf8b..8ff916f9e 100644 --- a/packages/docsearch-react/src/DocSearchButton.tsx +++ b/packages/docsearch-react/src/DocSearchButton.tsx @@ -10,6 +10,7 @@ export type ButtonTranslations = Partial<{ export type DocSearchButtonProps = React.ComponentProps<'button'> & { translations?: ButtonTranslations; + toggleKey?: string; }; const ACTION_KEY_DEFAULT = 'Ctrl' as const; @@ -22,7 +23,7 @@ function isAppleDevice() { export const DocSearchButton = React.forwardRef< HTMLButtonElement, DocSearchButtonProps ->(({ translations = {}, ...props }, ref) => { +>(({ translations = {}, toggleKey = 'k', ...props }, ref) => { const { buttonText = 'Search', buttonAriaLabel = 'Search' } = translations; const key = useMemo< @@ -53,7 +54,9 @@ export const DocSearchButton = React.forwardRef< {key === ACTION_KEY_DEFAULT ? : key} - K + + {toggleKey.toLocaleUpperCase()} + )} diff --git a/packages/docsearch-react/src/__tests__/api.test.tsx b/packages/docsearch-react/src/__tests__/api.test.tsx index 3b9dea95a..1d1b673a2 100644 --- a/packages/docsearch-react/src/__tests__/api.test.tsx +++ b/packages/docsearch-react/src/__tests__/api.test.tsx @@ -5,6 +5,7 @@ import { screen, act, } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import '@testing-library/jest-dom'; @@ -27,6 +28,44 @@ describe('api', () => { expect(document.querySelector('.DocSearch')).toBeInTheDocument(); }); + describe('toggleKey', () => { + it('opens and close the modal with the default shortcut', async () => { + render(); + + await waitFor(() => { + userEvent.keyboard('{meta}{k}'); + }); + + expect(document.querySelector('.DocSearch-Modal')).toBeInTheDocument(); + + await waitFor(() => { + userEvent.keyboard('{meta}{k}'); + }); + + expect( + document.querySelector('.DocSearch-Modal') + ).not.toBeInTheDocument(); + }); + + it('overrides the default shortcut', async () => { + render(); + + await waitFor(() => { + userEvent.keyboard('{meta}{u}'); + }); + + expect(document.querySelector('.DocSearch-Modal')).toBeInTheDocument(); + + await waitFor(() => { + userEvent.keyboard('{meta}{u}'); + }); + + expect( + document.querySelector('.DocSearch-Modal') + ).not.toBeInTheDocument(); + }); + }); + describe('translations', () => { it('overrides the default DocSearchButton text', () => { render( diff --git a/packages/docsearch-react/src/useDocSearchKeyboardEvents.ts b/packages/docsearch-react/src/useDocSearchKeyboardEvents.ts index 8a9919456..536d258d9 100644 --- a/packages/docsearch-react/src/useDocSearchKeyboardEvents.ts +++ b/packages/docsearch-react/src/useDocSearchKeyboardEvents.ts @@ -6,6 +6,7 @@ export interface UseDocSearchKeyboardEventsProps { onClose: () => void; onInput?: (event: KeyboardEvent) => void; searchButtonRef?: React.RefObject; + toggleKey: string; } function isEditingContent(event: KeyboardEvent): boolean { @@ -26,6 +27,7 @@ export function useDocSearchKeyboardEvents({ onClose, onInput, searchButtonRef, + toggleKey = 'k', }: UseDocSearchKeyboardEventsProps) { React.useEffect(() => { function onKeyDown(event: KeyboardEvent) { @@ -37,9 +39,9 @@ export function useDocSearchKeyboardEvents({ } } if ( - (event.keyCode === 27 && isOpen) || - // The `Cmd+K` shortcut both opens and closes the modal. - (event.key === 'k' && (event.metaKey || event.ctrlKey)) || + (event.key === 'Escape' && isOpen) || + // The `Cmd+toggleKey` shortcut both opens and closes the modal. + (event.key === toggleKey && (event.metaKey || event.ctrlKey)) || // The `/` shortcut opens but doesn't close the modal because it's // a character. (!isEditingContent(event) && event.key === '/' && !isOpen) @@ -69,5 +71,5 @@ export function useDocSearchKeyboardEvents({ return () => { window.removeEventListener('keydown', onKeyDown); }; - }, [isOpen, onOpen, onClose, onInput, searchButtonRef]); + }, [isOpen, onOpen, onClose, onInput, searchButtonRef, toggleKey]); } diff --git a/packages/website/docs/api.mdx b/packages/website/docs/api.mdx index 6f270aa44..7a8d0a87b 100644 --- a/packages/website/docs/api.mdx +++ b/packages/website/docs/api.mdx @@ -169,6 +169,11 @@ const translations: DocSearchTranslations = { +## `toggleKey` + +> `type: string` | `default: "k"` | **optional** + +Allow overriding the default key to open/close the DocSearch modal. @@ -309,6 +314,12 @@ const translations: DocSearchTranslations = { +## `toggleKey` + +> `type: string` | `default: "k"` | **optional** + +Allow overriding the default key to open/close the DocSearch modal. +