1
1
import { useSearchTerm } from '../search.store'
2
- import { useSearchQuery } from './useSearchQuery'
2
+ import { SearchResultItem , useSearchQuery } from './useSearchQuery'
3
3
import {
4
- EuiButton ,
4
+ useEuiFontSize ,
5
+ EuiHighlight ,
6
+ EuiLink ,
5
7
EuiLoadingSpinner ,
6
8
EuiSpacer ,
7
9
EuiText ,
8
10
useEuiTheme ,
11
+ EuiIcon ,
12
+ EuiPagination ,
9
13
} from '@elastic/eui'
10
14
import { css } from '@emotion/react'
15
+ import { useDebounce } from '@uidotdev/usehooks'
11
16
import * as React from 'react'
17
+ import { useEffect , useMemo , useState } from 'react'
12
18
13
19
export const SearchResults = ( ) => {
14
20
const searchTerm = useSearchTerm ( )
15
- const { data, error, isLoading } = useSearchQuery ( )
21
+ const [ activePage , setActivePage ] = useState ( 0 )
22
+ const debouncedSearchTerm = useDebounce ( searchTerm , 300 )
23
+ useEffect ( ( ) => {
24
+ setActivePage ( 0 )
25
+ } , [ debouncedSearchTerm ] )
26
+ const { data, error, isLoading, isFetching } = useSearchQuery ( {
27
+ searchTerm,
28
+ pageNumber : activePage + 1 ,
29
+ } )
16
30
const { euiTheme } = useEuiTheme ( )
17
31
18
32
if ( ! searchTerm ) {
@@ -23,88 +37,193 @@ export const SearchResults = () => {
23
37
return < div > Error loading search results: { error . message } </ div >
24
38
}
25
39
26
- if ( isLoading ) {
27
- return (
28
- < div >
29
- < EuiLoadingSpinner size = "s" /> Loading search results...
40
+ return (
41
+ < div >
42
+ < div
43
+ css = { css `
44
+ display : flex;
45
+ gap : ${ euiTheme . size . s } ;
46
+ align-items : center;
47
+ ` }
48
+ >
49
+ { isLoading || isFetching ? (
50
+ < EuiLoadingSpinner size = "s" />
51
+ ) : (
52
+ < EuiIcon type = "search" color = "subdued" size = "s" />
53
+ ) }
54
+ < EuiText size = "xs" >
55
+ Search results for{ ' ' }
56
+ < span
57
+ css = { css `
58
+ font-weight : ${ euiTheme . font . weight . bold } ;
59
+ ` }
60
+ >
61
+ { searchTerm }
62
+ </ span >
63
+ </ EuiText >
30
64
</ div >
31
- )
32
- }
65
+ < EuiSpacer size = "s" />
66
+ { data && (
67
+ < >
68
+ < ul >
69
+ { data . results . map ( ( result ) => (
70
+ < SearchResultListItem item = { result } />
71
+ ) ) }
72
+ </ ul >
73
+ < div
74
+ css = { css `
75
+ display : flex;
76
+ justify-content : flex-end;
77
+ ` }
78
+ >
79
+ < EuiPagination
80
+ aria-label = "Many pages example"
81
+ pageCount = { Math . min ( data . pageCount , 10 ) }
82
+ activePage = { activePage }
83
+ onPageClick = { ( activePage ) =>
84
+ setActivePage ( activePage )
85
+ }
86
+ />
87
+ </ div >
88
+ </ >
89
+ ) }
90
+ </ div >
91
+ )
92
+ }
33
93
34
- if ( ! data || data . results . length === 0 ) {
35
- return < EuiText size = "xs" > No results found for "{ searchTerm } "</ EuiText >
36
- }
94
+ interface SearchResultListItemProps {
95
+ item : SearchResultItem
96
+ }
97
+
98
+ function SearchResultListItem ( { item : result } : SearchResultListItemProps ) {
99
+ const { euiTheme } = useEuiTheme ( )
100
+ const searchTerm = useSearchTerm ( )
101
+ const highlightSearchTerms = useMemo (
102
+ ( ) => searchTerm . toLowerCase ( ) . split ( ' ' ) ,
103
+ [ searchTerm ]
104
+ )
37
105
38
- const buttonCss = css `
39
- border : none;
40
- vertical-align : top;
41
- justify-content : flex-start;
42
- block-size : 100% ;
43
- padding-block : 4px ;
44
- & > span {
45
- justify-content : flex-start;
46
- align-items : flex-start;
47
- }
48
- svg {
49
- color : ${ euiTheme . colors . textSubdued } ;
50
- }
51
- .euiIcon {
52
- margin-top : 4px ;
53
- }
54
- `
106
+ if ( highlightSearchTerms . includes ( 'esql' ) ) {
107
+ highlightSearchTerms . push ( 'es|ql' )
108
+ }
55
109
56
- const trimDescription = ( description : string ) => {
57
- const limit = 200
58
- return description . length > limit
59
- ? description . slice ( 0 , limit ) + '...'
60
- : description
110
+ if ( highlightSearchTerms . includes ( 'dotnet' ) ) {
111
+ highlightSearchTerms . push ( '.net' )
61
112
}
113
+ return (
114
+ < li key = { result . url } >
115
+ < div
116
+ tabIndex = { 0 }
117
+ css = { css `
118
+ display : flex;
119
+ align-items : flex-start;
120
+ gap : ${ euiTheme . size . s } ;
121
+ padding-inline : ${ euiTheme . size . s } ;
122
+ padding-block : ${ euiTheme . size . xs } ;
123
+ border-radius : ${ euiTheme . border . radius . small } ;
124
+ : hover {
125
+ background-color : ${ euiTheme . colors . backgroundTransparentSubdued } ;
126
+ ` }
127
+ >
128
+ < EuiIcon
129
+ type = "document"
130
+ color = "subdued"
131
+ css = { css `
132
+ margin-top : ${ euiTheme . size . xs } ;
133
+ ` }
134
+ />
135
+ < div
136
+ css = { css `
137
+ width : 100% ;
138
+ text-align : left;
139
+ ` }
140
+ >
141
+ < EuiLink
142
+ tabIndex = { - 1 }
143
+ href = { result . url }
144
+ css = { css `
145
+ .euiMark {
146
+ background-color : ${ euiTheme . colors
147
+ . backgroundLightWarning } ;
148
+ font-weight : inherit;
149
+ }
150
+ ` }
151
+ >
152
+ < EuiHighlight
153
+ search = { highlightSearchTerms }
154
+ highlightAll = { true }
155
+ >
156
+ { result . title }
157
+ </ EuiHighlight >
158
+ </ EuiLink >
159
+ < Breadcrumbs
160
+ parents = { result . parents }
161
+ highlightSearchTerms = { highlightSearchTerms }
162
+ />
163
+ </ div >
164
+ </ div >
165
+ </ li >
166
+ )
167
+ }
62
168
169
+ function Breadcrumbs ( {
170
+ parents,
171
+ highlightSearchTerms,
172
+ } : {
173
+ parents : SearchResultItem [ 'parents' ]
174
+ highlightSearchTerms : string [ ]
175
+ } ) {
176
+ const { euiTheme } = useEuiTheme ( )
177
+ const { fontSize : smallFontsize } = useEuiFontSize ( 'xs' )
63
178
return (
64
- < div
65
- css = { `
66
- li:not(:first-child) {
67
- margin-top: ${ euiTheme . size . xs } ;
68
- }
179
+ < ul
180
+ css = { css `
181
+ margin-top : 2px ;
182
+ display : flex;
183
+ gap : 0 ${ euiTheme . size . xs } ;
184
+ flex-wrap : wrap;
185
+ list-style : none;
69
186
` }
70
187
>
71
- < EuiText size = "xs" > Search Results for "{ searchTerm } "</ EuiText >
72
- < EuiSpacer size = "s" />
73
- < ul >
74
- { data . results . map ( ( result ) => (
75
- < li key = { result . url } >
76
- < EuiButton
77
- css = { buttonCss }
78
- iconType = "document"
79
- color = "text"
80
- size = "s"
81
- fullWidth
82
- >
83
- < div
188
+ { parents
189
+ . slice ( 1 ) // skip /docs
190
+ . map ( ( parent ) => (
191
+ < li
192
+ key = { 'breadcrumb-' + parent . url }
193
+ css = { css `
194
+ & : not (: last-child )::after {
195
+ content : '/' ;
196
+ margin-left : ${ euiTheme . size . xs } ;
197
+ font-size : ${ smallFontsize } ;
198
+ color : ${ euiTheme . colors . text } ;
199
+ margin-top : -1px ;
200
+ }
201
+ display : inline-flex;
202
+ ` }
203
+ >
204
+ < EuiLink href = { parent . url } color = "text" tabIndex = { - 1 } >
205
+ < EuiText
206
+ size = "xs"
207
+ color = "subdued"
84
208
css = { css `
85
- width : 100% ;
86
- text-align : left;
209
+ .euiMark {
210
+ background-color : transparent;
211
+ text-decoration : underline;
212
+ color : inherit;
213
+ font-weight : inherit;
214
+ }
87
215
` }
88
216
>
89
- { result . title }
90
- < EuiSpacer size = "xs" />
91
- < EuiText
92
- css = { css `
93
- text-wrap : pretty;
94
- ` }
95
- textAlign = "left"
96
- size = "xs"
97
- color = "subdued"
217
+ < EuiHighlight
218
+ search = { highlightSearchTerms }
219
+ highlightAll = { true }
98
220
>
99
- { trimDescription ( result . description ) }
100
- </ EuiText >
101
- </ div >
102
- </ EuiButton >
103
- { /*<EuiIcon type="document" color="subdued" />*/ }
104
- { /*<EuiText>{result.title}</EuiText>*/ }
221
+ { parent . title }
222
+ </ EuiHighlight >
223
+ </ EuiText >
224
+ </ EuiLink >
105
225
</ li >
106
226
) ) }
107
- </ ul >
108
- </ div >
227
+ </ ul >
109
228
)
110
229
}
0 commit comments