Skip to content

Commit d0bb839

Browse files
heiskrCopilot
andauthored
Add "/" keyboard shortcut to open search overlay (#62221)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e530576 commit d0bb839

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

src/fixtures/tests/playwright-rendering.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,41 @@ test('use sidebar to go to Hello World page', async ({ page }) => {
5252
await expect(page).toHaveTitle(/Hello World - GitHub Docs/)
5353
})
5454

55+
test('press "/" to open the search overlay', async ({ page }) => {
56+
await page.goto('/')
57+
await turnOffExperimentsInPage(page)
58+
59+
// Wait for the header search button to render, so the keydown listener is attached.
60+
await page.locator('[data-testid="search"]:visible').waitFor()
61+
62+
const searchInput = page.getByTestId('overlay-search-input')
63+
// The overlay (and its input) is not in the DOM until it's opened.
64+
await expect(searchInput).toHaveCount(0)
65+
66+
// Pressing "/" anywhere on the page opens the overlay and focuses the input.
67+
await page.keyboard.press('/')
68+
await expect(searchInput).toBeFocused()
69+
70+
// Escape closes it again.
71+
await page.keyboard.press('Escape')
72+
await expect(searchInput).toHaveCount(0)
73+
})
74+
75+
test('"/" typed inside the search input is a literal slash', async ({ page }) => {
76+
await page.goto('/')
77+
await turnOffExperimentsInPage(page)
78+
79+
await page.locator('[data-testid="search"]:visible').waitFor()
80+
81+
await page.keyboard.press('/')
82+
const searchInput = page.getByTestId('overlay-search-input')
83+
await expect(searchInput).toBeFocused()
84+
85+
// The "/" shortcut must not fire while typing in a field, so it is not swallowed.
86+
await page.keyboard.type('a/b')
87+
await expect(searchInput).toHaveValue('a/b')
88+
})
89+
5590
test('do a search from home page and click on "Foo" page', async ({ page }) => {
5691
test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search')
5792

src/frame/components/page-header/Header.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ export const Header = () => {
9292
return () => window.removeEventListener('keydown', close)
9393
}, [])
9494

95+
// Pressing "/" anywhere on the page opens the search overlay, matching the
96+
// shortcut on github.com. Ignore the key when the user is typing in a form
97+
// field or editable element so we never swallow a literal "/", and when a
98+
// modifier is held so we don't clash with browser or OS shortcuts.
99+
useEffect(() => {
100+
const openOnSlash = (e: KeyboardEvent) => {
101+
if (e.key !== '/' || e.ctrlKey || e.metaKey || e.altKey) {
102+
return
103+
}
104+
const target = e.target as HTMLElement | null
105+
const tagName = target?.tagName
106+
if (
107+
tagName === 'INPUT' ||
108+
tagName === 'TEXTAREA' ||
109+
tagName === 'SELECT' ||
110+
target?.isContentEditable
111+
) {
112+
return
113+
}
114+
e.preventDefault()
115+
setIsSearchOpen(true)
116+
}
117+
window.addEventListener('keydown', openOnSlash)
118+
return () => window.removeEventListener('keydown', openOnSlash)
119+
}, [])
120+
95121
// For the UI in smaller browser widths, focus the picker menu button when
96122
// the sidebar is closed (not when the search overlay is closed — the
97123
// overlay's returnFocusRef handles focus restoration to the search button).

0 commit comments

Comments
 (0)