@@ -19,15 +19,16 @@ import Button from '@leafygreen-ui/button';
19
19
import IconButton from '@leafygreen-ui/icon-button' ;
20
20
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider' ;
21
21
import TextInput from '@leafygreen-ui/text-input' ;
22
+ import { Body } from '@leafygreen-ui/typography' ;
22
23
23
24
import { Icon } from '../../../icon/src/Icon' ;
24
25
25
26
import {
26
27
allButtonStyles ,
27
28
closeButtonStyles ,
28
29
findInputContainerStyles ,
29
- findInputIconButtonStyles ,
30
30
findInputStyles ,
31
+ findOptionsContainerStyles ,
31
32
findSectionStyles ,
32
33
getContainerStyles ,
33
34
getReplaceInnerSectionStyles ,
@@ -44,6 +45,7 @@ export function SearchForm({ view }: SearchFormProps) {
44
45
const [ searchString , setSearchString ] = useState ( '' ) ;
45
46
const [ findCount , setFindCount ] = useState ( 0 ) ;
46
47
const { theme } = useDarkMode ( ) ;
48
+ const [ selectedIndex , setSelectedIndex ] = useState < number | null > ( null ) ;
47
49
48
50
const handleToggleButtonClick = useCallback (
49
51
( _e : MouseEvent < HTMLButtonElement > ) => {
@@ -66,6 +68,35 @@ export function SearchForm({ view }: SearchFormProps) {
66
68
[ ] ,
67
69
) ;
68
70
71
+ const computeSelectedIndex = useCallback ( ( ) => {
72
+ const query = new SearchQuery ( {
73
+ search : searchString ,
74
+ caseSensitive : true ,
75
+ regexp : false ,
76
+ wholeWord : false ,
77
+ replace : '' ,
78
+ } ) ;
79
+
80
+ const cursor = query . getCursor ( view . state . doc ) ;
81
+ const selection = view . state . selection . main ;
82
+
83
+ let index = 1 ;
84
+ let result = cursor . next ( ) ;
85
+
86
+ while ( ! result . done ) {
87
+ if (
88
+ result . value . from === selection . from &&
89
+ result . value . to === selection . to
90
+ ) {
91
+ return index ;
92
+ }
93
+ index ++ ;
94
+ result = cursor . next ( ) ;
95
+ }
96
+
97
+ return null ;
98
+ } , [ searchString , view ] ) ;
99
+
69
100
useEffect ( ( ) => {
70
101
const query = new SearchQuery ( {
71
102
search : searchString ,
@@ -87,16 +118,48 @@ export function SearchForm({ view }: SearchFormProps) {
87
118
}
88
119
89
120
setFindCount ( count ) ;
90
- } , [ searchString , view ] ) ;
121
+
122
+ // Update selected index if current selection matches one of the results
123
+ const selection = view . state . selection . main ;
124
+ const cursor2 = query . getCursor ( view . state . doc ) ;
125
+ let idx = 1 ;
126
+ let res2 = cursor2 . next ( ) ;
127
+ let currentIndex : number | null = null ;
128
+
129
+ while ( ! res2 . done ) {
130
+ if (
131
+ res2 . value . from === selection . from &&
132
+ res2 . value . to === selection . to
133
+ ) {
134
+ currentIndex = idx ;
135
+ break ;
136
+ }
137
+ idx ++ ;
138
+ res2 = cursor2 . next ( ) ;
139
+ }
140
+
141
+ setSelectedIndex ( currentIndex ) ;
142
+ } , [ searchString , view , computeSelectedIndex ] ) ;
91
143
92
144
const handleFindFormSubmit = useCallback (
93
145
( e : FormEvent < HTMLFormElement > ) => {
94
146
e . preventDefault ( ) ;
95
147
findNext ( view ) ;
148
+ setSelectedIndex ( computeSelectedIndex ( ) ) ;
96
149
} ,
97
- [ view ] ,
150
+ [ view , computeSelectedIndex ] ,
98
151
) ;
99
152
153
+ const handleNextClick = useCallback ( ( ) => {
154
+ findNext ( view ) ;
155
+ setSelectedIndex ( computeSelectedIndex ( ) ) ;
156
+ } , [ view , computeSelectedIndex ] ) ;
157
+
158
+ const handlePreviousClick = useCallback ( ( ) => {
159
+ findPrevious ( view ) ;
160
+ setSelectedIndex ( computeSelectedIndex ( ) ) ;
161
+ } , [ view , computeSelectedIndex ] ) ;
162
+
100
163
return (
101
164
< div
102
165
className = { getContainerStyles ( { theme, isOpen } ) }
@@ -123,31 +186,38 @@ export function SearchForm({ view }: SearchFormProps) {
123
186
value = { searchString }
124
187
/>
125
188
</ form >
126
- < IconButton
127
- className = { findInputIconButtonStyles }
128
- aria-label = "filter button"
129
- >
130
- < Icon glyph = "Filter" />
131
- </ IconButton >
189
+ < div className = { findOptionsContainerStyles } >
190
+ { searchString && (
191
+ < Body >
192
+ { selectedIndex ?? '?' } /{ findCount }
193
+ </ Body >
194
+ ) }
195
+ < IconButton aria-label = "filter button" >
196
+ < Icon glyph = "Filter" />
197
+ </ IconButton >
198
+ </ div >
132
199
</ div >
133
200
< IconButton
134
201
aria-label = "previous item button"
135
202
disabled = { ! searchString || findCount === 0 }
136
- onClick = { ( ) => findPrevious ( view ) }
203
+ onClick = { handlePreviousClick }
137
204
>
138
205
< Icon glyph = "ArrowUp" />
139
206
</ IconButton >
140
207
< IconButton
141
208
aria-label = "next item button"
142
209
disabled = { ! searchString || findCount === 0 }
143
- onClick = { ( ) => findNext ( view ) }
210
+ onClick = { handleNextClick }
144
211
>
145
212
< Icon glyph = "ArrowDown" />
146
213
</ IconButton >
147
214
< Button
148
215
className = { allButtonStyles }
149
216
disabled = { ! searchString || findCount === 0 }
150
- onClick = { ( ) => selectMatches ( view ) }
217
+ onClick = { ( ) => {
218
+ selectMatches ( view ) ;
219
+ setSelectedIndex ( null ) ;
220
+ } }
151
221
>
152
222
All
153
223
</ Button >
0 commit comments