1
1
'use client' ;
2
2
3
- import {
4
- Form ,
5
- FormControl ,
6
- FormField ,
7
- FormItem ,
8
- FormMessage ,
9
- } from "@/components/ui/form" ;
10
- import { Input } from "@/components/ui/input" ;
3
+ import { useTailwind } from "@/hooks/useTailwind" ;
4
+ import { SearchQueryParams } from "@/lib/types" ;
11
5
import { cn , createPathWithQueryParams } from "@/lib/utils" ;
12
- import { zodResolver } from "@hookform/resolvers/zod" ;
6
+ import {
7
+ cursorCharLeft ,
8
+ cursorCharRight ,
9
+ cursorDocEnd ,
10
+ cursorDocStart ,
11
+ cursorLineBoundaryBackward ,
12
+ cursorLineBoundaryForward ,
13
+ deleteCharBackward ,
14
+ deleteCharForward ,
15
+ deleteGroupBackward ,
16
+ deleteGroupForward ,
17
+ deleteLineBoundaryBackward ,
18
+ deleteLineBoundaryForward ,
19
+ history ,
20
+ historyKeymap ,
21
+ selectAll ,
22
+ selectCharLeft ,
23
+ selectCharRight ,
24
+ selectDocEnd ,
25
+ selectDocStart ,
26
+ selectLineBoundaryBackward ,
27
+ selectLineBoundaryForward
28
+ } from "@codemirror/commands" ;
29
+ import { LanguageSupport , StreamLanguage } from "@codemirror/language" ;
30
+ import { tags as t } from '@lezer/highlight' ;
31
+ import { createTheme } from '@uiw/codemirror-themes' ;
32
+ import CodeMirror , { KeyBinding , keymap , ReactCodeMirrorRef } from "@uiw/react-codemirror" ;
13
33
import { cva } from "class-variance-authority" ;
14
34
import { useRouter } from "next/navigation" ;
15
- import { useForm } from "react-hook-form" ;
16
- import { z } from "zod" ;
17
- import { useHotkeys } from 'react-hotkeys-hook'
18
- import { useRef } from "react" ;
19
- import { SearchQueryParams } from "@/lib/types" ;
35
+ import { useMemo , useRef , useState } from "react" ;
36
+ import { useHotkeys } from 'react-hotkeys-hook' ;
20
37
21
38
interface SearchBarProps {
22
39
className ?: string ;
@@ -25,12 +42,53 @@ interface SearchBarProps {
25
42
autoFocus ?: boolean ;
26
43
}
27
44
28
- const formSchema = z . object ( {
29
- query : z . string ( ) ,
45
+ const searchBarKeymap : readonly KeyBinding [ ] = ( [
46
+ { key : "ArrowLeft" , run : cursorCharLeft , shift : selectCharLeft , preventDefault : true } ,
47
+ { key : "ArrowRight" , run : cursorCharRight , shift : selectCharRight , preventDefault : true } ,
48
+
49
+ { key : "Home" , run : cursorLineBoundaryBackward , shift : selectLineBoundaryBackward , preventDefault : true } ,
50
+ { key : "Mod-Home" , run : cursorDocStart , shift : selectDocStart } ,
51
+
52
+ { key : "End" , run : cursorLineBoundaryForward , shift : selectLineBoundaryForward , preventDefault : true } ,
53
+ { key : "Mod-End" , run : cursorDocEnd , shift : selectDocEnd } ,
54
+
55
+ { key : "Mod-a" , run : selectAll } ,
56
+
57
+ { key : "Backspace" , run : deleteCharBackward , shift : deleteCharBackward } ,
58
+ { key : "Delete" , run : deleteCharForward } ,
59
+ { key : "Mod-Backspace" , mac : "Alt-Backspace" , run : deleteGroupBackward } ,
60
+ { key : "Mod-Delete" , mac : "Alt-Delete" , run : deleteGroupForward } ,
61
+ { mac : "Mod-Backspace" , run : deleteLineBoundaryBackward } ,
62
+ { mac : "Mod-Delete" , run : deleteLineBoundaryForward }
63
+ ] as KeyBinding [ ] ) . concat ( historyKeymap ) ;
64
+
65
+ const zoektLanguage = StreamLanguage . define ( {
66
+ token : ( stream ) => {
67
+ if ( stream . match ( / - ? ( f i l e | b r a n c h | r e v i s i o n | r e v | c a s e | r e p o | l a n g | c o n t e n t | s y m ) : / ) ) {
68
+ return t . keyword . toString ( ) ;
69
+ }
70
+
71
+ if ( stream . match ( / \b o r \b / ) ) {
72
+ return t . keyword . toString ( ) ;
73
+ }
74
+
75
+ stream . next ( ) ;
76
+ return null ;
77
+ } ,
30
78
} ) ;
31
79
80
+ const zoekt = ( ) => {
81
+ return new LanguageSupport ( zoektLanguage ) ;
82
+ }
83
+
84
+ const extensions = [
85
+ keymap . of ( searchBarKeymap ) ,
86
+ history ( ) ,
87
+ zoekt ( )
88
+ ] ;
89
+
32
90
const searchBarVariants = cva (
33
- "w-full" ,
91
+ "flex items-center w-full p-0.5 border rounded-md " ,
34
92
{
35
93
variants : {
36
94
size : {
@@ -42,63 +100,79 @@ const searchBarVariants = cva(
42
100
size : "default" ,
43
101
}
44
102
}
45
- )
103
+ ) ;
46
104
47
105
export const SearchBar = ( {
48
106
className,
49
107
size,
50
108
defaultQuery,
51
109
autoFocus,
52
110
} : SearchBarProps ) => {
111
+ const router = useRouter ( ) ;
112
+ const tailwind = useTailwind ( ) ;
113
+
114
+ const theme = useMemo ( ( ) => {
115
+ return createTheme ( {
116
+ theme : 'light' ,
117
+ settings : {
118
+ background : tailwind . theme . colors . background ,
119
+ foreground : tailwind . theme . colors . foreground ,
120
+ caret : '#AEAFAD' ,
121
+ } ,
122
+ styles : [
123
+ {
124
+ tag : t . keyword ,
125
+ color : tailwind . theme . colors . highlight ,
126
+ } ,
127
+ ] ,
128
+ } ) ;
129
+ } , [ tailwind ] ) ;
130
+
131
+ const [ query , setQuery ] = useState ( defaultQuery ?? "" ) ;
132
+ const editorRef = useRef < ReactCodeMirrorRef > ( null ) ;
53
133
54
- const inputRef = useRef < HTMLInputElement > ( null ) ;
55
134
useHotkeys ( '/' , ( event ) => {
56
135
event . preventDefault ( ) ;
57
- inputRef . current ?. focus ( ) ;
58
- } ) ;
59
-
60
- const router = useRouter ( ) ;
61
- const form = useForm < z . infer < typeof formSchema > > ( {
62
- resolver : zodResolver ( formSchema ) ,
63
- defaultValues : {
64
- query : defaultQuery ?? "" ,
136
+ editorRef . current ?. view ?. focus ( ) ;
137
+ if ( editorRef . current ?. view ) {
138
+ cursorDocEnd ( {
139
+ state : editorRef . current . view . state ,
140
+ dispatch : editorRef . current . view . dispatch ,
141
+ } ) ;
65
142
}
66
143
} ) ;
67
144
68
- const onSubmit = ( values : z . infer < typeof formSchema > ) => {
145
+ const onSubmit = ( ) => {
69
146
const url = createPathWithQueryParams ( '/search' ,
70
- [ SearchQueryParams . query , values . query ] ,
147
+ [ SearchQueryParams . query , query ] ,
71
148
)
72
149
router . push ( url ) ;
73
150
}
74
151
75
152
return (
76
- < Form { ...form } >
77
- < form
78
- onSubmit = { form . handleSubmit ( onSubmit ) }
79
- className = "w-full"
80
- >
81
- < FormField
82
- control = { form . control }
83
- name = "query"
84
- render = { ( { field } ) => (
85
- < FormItem >
86
- < FormControl >
87
- < Input
88
- placeholder = "Search..."
89
- className = { cn ( searchBarVariants ( { size, className } ) ) }
90
- { ...field }
91
- ref = { inputRef }
92
- autoFocus = { autoFocus ?? false }
93
- // This is needed to prevent mobile browsers from zooming in when the input is focused
94
- style = { { fontSize : '1rem' } }
95
- />
96
- </ FormControl >
97
- < FormMessage />
98
- </ FormItem >
99
- ) }
100
- />
101
- </ form >
102
- </ Form >
153
+ < div
154
+ className = { cn ( searchBarVariants ( { size, className } ) ) }
155
+ onKeyDown = { ( e ) => {
156
+ if ( e . key === 'Enter' ) {
157
+ e . preventDefault ( ) ;
158
+ onSubmit ( ) ;
159
+ }
160
+ } }
161
+ >
162
+ < CodeMirror
163
+ ref = { editorRef }
164
+ className = "grow"
165
+ placeholder = { "Search..." }
166
+ value = { query }
167
+ onChange = { ( value ) => {
168
+ setQuery ( value ) ;
169
+ } }
170
+ theme = { theme }
171
+ basicSetup = { false }
172
+ extensions = { extensions }
173
+ indentWithTab = { false }
174
+ autoFocus = { autoFocus ?? false }
175
+ />
176
+ </ div >
103
177
)
104
178
}
0 commit comments