Skip to content

Commit 15b63f2

Browse files
committed
Change search bind
1 parent 1da9953 commit 15b63f2

4 files changed

Lines changed: 76 additions & 23 deletions

File tree

src/components/DeclarationsPage.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo, useState } from "react";
1+
import React, { useCallback, useEffect, useMemo, useState } from "react";
22
import { styled } from "@linaria/react";
33
import { DeclarationsContext, DeclarationsContextType } from "./schema/DeclarationsContext";
44
import { DeclarationsSidebar } from "./DeclarationsSidebar";
@@ -13,6 +13,30 @@ export default function DeclarationsPage({ context }: { context: DeclarationsCon
1313
const [sidebarOpen, setSidebarOpen] = useState(false);
1414
const search = useHashParam("search") ?? "";
1515

16+
useEffect(() => {
17+
const listener = (e: KeyboardEvent) => {
18+
if (e.key !== "/" || e.ctrlKey || e.metaKey || e.altKey) return;
19+
const active = document.activeElement;
20+
const mainSearch = document.getElementById("main-search");
21+
const sidebarFilter = document.getElementById("sidebar-filter");
22+
23+
if (active === mainSearch) {
24+
e.preventDefault();
25+
sidebarFilter?.focus();
26+
} else if (active === sidebarFilter) {
27+
// Explicit check before the HTMLInputElement guard below,
28+
// otherwise "/" would just type into the filter input.
29+
e.preventDefault();
30+
mainSearch?.focus();
31+
} else if (!(active instanceof HTMLInputElement)) {
32+
e.preventDefault();
33+
mainSearch?.focus();
34+
}
35+
};
36+
document.addEventListener("keydown", listener);
37+
return () => document.removeEventListener("keydown", listener);
38+
}, []);
39+
1640
const searchCtx = useMemo(() => ({ search }), [search]);
1741
const filterCtx = useMemo(() => ({ filter, setFilter }), [filter]);
1842

src/components/DeclarationsSidebar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void })
207207
<GameSwitcher currentGame={game} />
208208
</SidebarBrandRow>
209209
<SidebarSearchInput
210+
id="sidebar-filter"
210211
type="search"
211212
placeholder="Filter…"
212213
value={filter}

src/components/search/SearchBox.tsx

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,7 @@ import { useLocation, useNavigate, href } from "react-router";
33
import { styled } from "@linaria/react";
44
import { SearchContext } from "./SearchContext";
55
import { DeclarationsContext } from "../schema/DeclarationsContext";
6-
import { KindIcon, IconKind } from "../kind-icon/KindIcon";
7-
8-
export function useCtrlFHook<T extends HTMLElement>() {
9-
const ref = useRef<T | null>(null);
10-
useEffect(() => {
11-
const listener = (event: KeyboardEvent) => {
12-
if (ref.current && (event.ctrlKey || event.metaKey) && event.key === "f") {
13-
// Use default CTRL+F only when element already has focus
14-
if (document.activeElement !== ref.current) event.preventDefault();
15-
ref.current.focus();
16-
}
17-
};
18-
19-
document.addEventListener("keydown", listener);
20-
return () => document.removeEventListener("keydown", listener);
21-
}, []);
22-
23-
return ref;
24-
}
6+
import { KindIcon, IconKind, ICONS_URL } from "../kind-icon/KindIcon";
257

268
export const SearchInput = styled.input`
279
width: 100%;
@@ -130,6 +112,43 @@ const SearchBoxWrapper = styled.div`
130112
width: 100%;
131113
`;
132114

115+
const SearchIcon = styled.svg`
116+
position: absolute;
117+
left: 12px;
118+
top: 50%;
119+
translate: 0 -50%;
120+
pointer-events: none;
121+
color: var(--searchbox-placeholder);
122+
`;
123+
124+
const MainSearchInput = styled(SearchInput)`
125+
padding-left: 36px;
126+
`;
127+
128+
const SearchPlaceholder = styled.div`
129+
position: absolute;
130+
inset: 0;
131+
display: flex;
132+
align-items: center;
133+
gap: 4px;
134+
padding: 0 14px 0 36px;
135+
pointer-events: none;
136+
color: var(--searchbox-placeholder);
137+
font-size: 14px;
138+
font-family: inherit;
139+
140+
kbd {
141+
font-family: inherit;
142+
font-size: 12px;
143+
font-weight: 600;
144+
padding: 1px 6px;
145+
border-radius: 4px;
146+
border: 1px solid var(--searchbox-placeholder);
147+
opacity: 0.6;
148+
line-height: 1.4;
149+
}
150+
`;
151+
133152
const TagPopup = styled.div`
134153
position: absolute;
135154
top: calc(100% + 4px);
@@ -414,13 +433,21 @@ export function SearchBox({ className }: { className?: string }) {
414433
}
415434
};
416435

417-
const ref = useCtrlFHook<HTMLInputElement>();
436+
const ref = useRef<HTMLInputElement>(null);
418437

419438
return (
420439
<SearchBoxWrapper className={className}>
421-
<SearchInput
440+
<SearchIcon width="16" height="16">
441+
<use href={`${ICONS_URL}#search`} />
442+
</SearchIcon>
443+
{!inputValue && !isFocused && (
444+
<SearchPlaceholder>
445+
Type <kbd>/</kbd> to search
446+
</SearchPlaceholder>
447+
)}
448+
<MainSearchInput
449+
id="main-search"
422450
type="search"
423-
placeholder="Search…"
424451
ref={ref}
425452
value={inputValue}
426453
onChange={onChange}

src/icons.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)