Skip to content

Commit ee65c53

Browse files
committed
feat: add keyboard support to the search
1 parent ab3be4c commit ee65c53

File tree

2 files changed

+63
-23
lines changed

2 files changed

+63
-23
lines changed

src/components/databrowser/components/sidebar/index.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import { KeysList } from "./keys-list"
1515
import { SearchInput } from "./search-input"
1616
import { LoadingSkeleton } from "./skeleton-buttons"
1717
import { DataTypeSelector } from "./type-selector"
18-
import {
19-
DropdownMenu,
20-
DropdownMenuContent,
21-
DropdownMenuTrigger,
22-
} from "@/components/ui/dropdown-menu"
2318

2419
export function Sidebar() {
2520
const { keys, query } = useKeys()

src/components/databrowser/components/sidebar/search-input.tsx

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react"
1+
import { useState, useRef, useEffect, type KeyboardEvent } from "react"
22
import { IconX } from "@tabler/icons-react"
33

44
import { Button } from "@/components/ui/button"
@@ -20,7 +20,12 @@ export const SearchInput = () => {
2020
const { setSearchKey, search } = useTab()
2121
const { searchHistory, addSearchHistory } = useDatabrowserStore()
2222
const [state, setState] = useState(search.key)
23+
2324
const [isFocus, setIsFocus] = useState(false)
25+
const [focusedIndex, setFocusedIndex] = useState(-1)
26+
27+
const inputRef = useRef<HTMLInputElement>(null)
28+
const historyItemRefs = useRef<(HTMLButtonElement | null)[]>([])
2429

2530
const handleSubmit = (value: string) => {
2631
if (value.trim() !== "" && !value.includes("*")) value = `${value}*`
@@ -29,52 +34,92 @@ export const SearchInput = () => {
2934
setState(value)
3035
}
3136

32-
const handleItemSelect = (value: string) => {
33-
addSearchHistory(value)
34-
setSearchKey(value)
35-
setState(value)
36-
}
37-
3837
const filteredHistory = dedupeSearchHistory(
3938
searchHistory.filter((item) => item.includes(state) && item !== state)
40-
).slice(0, 5)
39+
)
40+
.slice(0, 5)
41+
// If it has a * in the end, remove it
42+
.map((item) => (item.endsWith("*") ? item.slice(0, -1) : item))
43+
44+
// Reset focused index when filtered history changes
45+
useEffect(() => {
46+
setFocusedIndex(-1)
47+
}, [filteredHistory.length])
48+
49+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
50+
if (e.key === "Enter") {
51+
const text =
52+
focusedIndex >= 0 && focusedIndex < filteredHistory.length
53+
? filteredHistory[focusedIndex]
54+
: e.currentTarget.value
55+
handleSubmit(text)
56+
} else if (e.key === "Escape") {
57+
setState("")
58+
setFocusedIndex(-1)
59+
inputRef.current?.blur()
60+
} else if (e.key === "ArrowDown" || (e.key === "Tab" && !e.shiftKey)) {
61+
e.preventDefault()
62+
if (focusedIndex < filteredHistory.length - 1) {
63+
setFocusedIndex(focusedIndex + 1)
64+
} else if (filteredHistory.length > 0) {
65+
setFocusedIndex(0)
66+
}
67+
} else if (e.key === "ArrowUp" || (e.key === "Tab" && e.shiftKey)) {
68+
e.preventDefault()
69+
if (focusedIndex > 0) {
70+
setFocusedIndex(focusedIndex - 1)
71+
} else if (filteredHistory.length > 0 && focusedIndex === 0) {
72+
setFocusedIndex(-1)
73+
inputRef.current?.focus()
74+
} else if (filteredHistory.length > 0) {
75+
setFocusedIndex(filteredHistory.length - 1)
76+
}
77+
}
78+
}
4179

4280
return (
4381
<div className="relative grow">
4482
<Popover open={isFocus && filteredHistory.length > 0}>
4583
<PopoverTrigger asChild>
4684
<div>
4785
<Input
86+
ref={inputRef}
4887
placeholder="Search"
4988
className={"rounded-l-none border-zinc-300 font-normal"}
50-
onKeyDown={(e) => {
51-
if (e.key === "Enter") handleSubmit(e.currentTarget.value)
52-
else if (e.key === "Escape") setState("")
53-
}}
89+
onKeyDown={handleKeyDown}
5490
onChange={(e) => {
5591
setState(e.currentTarget.value)
5692
if (e.currentTarget.value.trim() === "") handleSubmit("")
5793
}}
5894
value={state}
59-
onFocus={() => setIsFocus(true)}
95+
onFocus={() => {
96+
setIsFocus(true)
97+
setFocusedIndex(-1)
98+
}}
6099
onBlur={() => setIsFocus(false)}
61100
/>
62101
</div>
63102
</PopoverTrigger>
64103

65104
<PopoverContent
66-
className="w-[200px] divide-y px-3 py-2 text-[13px] text-zinc-900"
105+
className="w-[--radix-popover-trigger-width] divide-y px-3 py-2 text-[13px] text-zinc-900"
67106
autoFocus={false}
68107
onOpenAutoFocus={(e) => {
69108
e.preventDefault()
70109
e.stopPropagation()
71110
}}
72111
>
73-
{filteredHistory.map((item) => (
74-
<div key={item} className="w-full py-[2px]">
112+
{filteredHistory.map((item, index) => (
113+
<div key={item} className="w-full py-[3px]">
75114
<button
76-
onClick={() => handleItemSelect(item)}
77-
className="block w-full rounded-sm p-1 text-left transition-colors hover:bg-zinc-100"
115+
ref={(el) => {
116+
historyItemRefs.current[index] = el
117+
}}
118+
onClick={() => handleSubmit(item)}
119+
onMouseEnter={() => setFocusedIndex(index)}
120+
className={`block w-full rounded-sm p-1 text-left transition-colors ${
121+
focusedIndex === index ? "bg-zinc-100" : "hover:bg-zinc-100"
122+
}`}
78123
>
79124
{item}
80125
</button>

0 commit comments

Comments
 (0)