11import { useSearchTerm } from '../search.store'
2- import { useSearchQuery } from './useSearchQuery'
2+ import { SearchResultItem , useSearchQuery } from './useSearchQuery'
33import {
4- EuiButton ,
4+ useEuiFontSize ,
5+ EuiHighlight ,
6+ EuiLink ,
57 EuiLoadingSpinner ,
68 EuiSpacer ,
79 EuiText ,
810 useEuiTheme ,
11+ EuiIcon ,
12+ EuiPagination ,
913} from '@elastic/eui'
1014import { css } from '@emotion/react'
15+ import { useDebounce } from '@uidotdev/usehooks'
1116import * as React from 'react'
17+ import { useEffect , useMemo , useState } from 'react'
1218
1319export const SearchResults = ( ) => {
1420 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+ } )
1630 const { euiTheme } = useEuiTheme ( )
1731
1832 if ( ! searchTerm ) {
@@ -23,88 +37,193 @@ export const SearchResults = () => {
2337 return < div > Error loading search results: { error . message } </ div >
2438 }
2539
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 >
3064 </ 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+ }
3393
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+ )
37105
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+ }
55109
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' )
61112 }
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+ }
62168
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' )
63178 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;
69186 ` }
70187 >
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"
84208 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+ }
87215 ` }
88216 >
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 }
98220 >
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 >
105225 </ li >
106226 ) ) }
107- </ ul >
108- </ div >
227+ </ ul >
109228 )
110229}
0 commit comments