1
- import { useState } from "react"
1
+ import { useState , useRef , useEffect , type KeyboardEvent } from "react"
2
2
import { IconX } from "@tabler/icons-react"
3
3
4
4
import { Button } from "@/components/ui/button"
@@ -20,7 +20,12 @@ export const SearchInput = () => {
20
20
const { setSearchKey, search } = useTab ( )
21
21
const { searchHistory, addSearchHistory } = useDatabrowserStore ( )
22
22
const [ state , setState ] = useState ( search . key )
23
+
23
24
const [ isFocus , setIsFocus ] = useState ( false )
25
+ const [ focusedIndex , setFocusedIndex ] = useState ( - 1 )
26
+
27
+ const inputRef = useRef < HTMLInputElement > ( null )
28
+ const historyItemRefs = useRef < ( HTMLButtonElement | null ) [ ] > ( [ ] )
24
29
25
30
const handleSubmit = ( value : string ) => {
26
31
if ( value . trim ( ) !== "" && ! value . includes ( "*" ) ) value = `${ value } *`
@@ -29,52 +34,92 @@ export const SearchInput = () => {
29
34
setState ( value )
30
35
}
31
36
32
- const handleItemSelect = ( value : string ) => {
33
- addSearchHistory ( value )
34
- setSearchKey ( value )
35
- setState ( value )
36
- }
37
-
38
37
const filteredHistory = dedupeSearchHistory (
39
38
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
+ }
41
79
42
80
return (
43
81
< div className = "relative grow" >
44
82
< Popover open = { isFocus && filteredHistory . length > 0 } >
45
83
< PopoverTrigger asChild >
46
84
< div >
47
85
< Input
86
+ ref = { inputRef }
48
87
placeholder = "Search"
49
88
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 }
54
90
onChange = { ( e ) => {
55
91
setState ( e . currentTarget . value )
56
92
if ( e . currentTarget . value . trim ( ) === "" ) handleSubmit ( "" )
57
93
} }
58
94
value = { state }
59
- onFocus = { ( ) => setIsFocus ( true ) }
95
+ onFocus = { ( ) => {
96
+ setIsFocus ( true )
97
+ setFocusedIndex ( - 1 )
98
+ } }
60
99
onBlur = { ( ) => setIsFocus ( false ) }
61
100
/>
62
101
</ div >
63
102
</ PopoverTrigger >
64
103
65
104
< 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"
67
106
autoFocus = { false }
68
107
onOpenAutoFocus = { ( e ) => {
69
108
e . preventDefault ( )
70
109
e . stopPropagation ( )
71
110
} }
72
111
>
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 ]" >
75
114
< 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
+ } `}
78
123
>
79
124
{ item }
80
125
</ button >
0 commit comments