@@ -4,6 +4,7 @@ import Link from 'next/link';
4
4
import { alpha , ListItemButton , useMediaQuery , Box } from '@mui/material' ;
5
5
import LaunchIcon from '@mui/icons-material/Launch' ;
6
6
import { type RouteItem } from './routes' ;
7
+ import highlightWords from 'highlight-words' ;
7
8
8
9
interface Props {
9
10
depth ?: number ;
@@ -12,6 +13,7 @@ interface Props {
12
13
routes : RouteItem [ ] ;
13
14
setNavOpen : ( navOpen : boolean ) => void ;
14
15
setSearch : ( search : string ) => void ;
16
+ search : string ;
15
17
}
16
18
17
19
export const SideBarItems = ( {
@@ -21,6 +23,7 @@ export const SideBarItems = ({
21
23
routes,
22
24
setNavOpen,
23
25
setSearch,
26
+ search,
24
27
} : Props ) => {
25
28
const { pathname } = useRouter ( ) ;
26
29
const isMobile = useMediaQuery ( '(max-width: 900px)' ) ;
@@ -39,14 +42,28 @@ export const SideBarItems = ({
39
42
return (
40
43
< >
41
44
{ routes . map (
42
- ( { href, items, label, divider, external, secondaryItems } ) => {
45
+ ( {
46
+ href,
47
+ items,
48
+ label,
49
+ divider,
50
+ external,
51
+ secondaryItems,
52
+ keywords,
53
+ } ) => {
43
54
const secondaryHrefs = secondaryItems ?. map ( ( i ) => i . href ) ;
44
55
45
56
const isSelected = pathname === href ;
46
57
const isSelectedParent = secondaryHrefs ?. includes ( pathname ) ;
47
58
const willBeSecondary =
48
59
! ! secondaryItems ?. length && ( isSelectedParent || expandAll ) ;
49
60
61
+ const matchingKeyWords = search
62
+ ? keywords ?. filter ( ( word ) =>
63
+ word . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ,
64
+ )
65
+ : undefined ;
66
+
50
67
return (
51
68
< Fragment key = { label } >
52
69
< Link href = { href } target = { external ? '_blank' : undefined } >
@@ -83,7 +100,11 @@ export const SideBarItems = ({
83
100
? '1.25rem'
84
101
: '1rem' ,
85
102
fontWeight : isSelectedParent ? 'bold' : 'normal' ,
86
- height : items ? '2.5rem' : '2rem' ,
103
+ height : matchingKeyWords ?. length
104
+ ? '3.5rem'
105
+ : items
106
+ ? '2.5rem'
107
+ : '2rem' ,
87
108
lineHeight : depth === 0 && ! items ? '1.25rem' : '0.75rem' ,
88
109
padding : '0' ,
89
110
whiteSpace : 'nowrap' ,
@@ -97,7 +118,11 @@ export const SideBarItems = ({
97
118
ml : `${ depth } rem` ,
98
119
} }
99
120
>
100
- { label }
121
+ < HighlightedText label = { label } search = { search } />
122
+ < HighlightedKeywords
123
+ keywords = { matchingKeyWords }
124
+ search = { search }
125
+ />
101
126
{ external && (
102
127
< LaunchIcon fontSize = "small" sx = { { m : '-0.25rem 4px' } } />
103
128
) }
@@ -111,6 +136,7 @@ export const SideBarItems = ({
111
136
isSecondary = { willBeSecondary }
112
137
routes = { willBeSecondary ? secondaryItems : items ! }
113
138
setNavOpen = { setNavOpen }
139
+ search = { search }
114
140
setSearch = { setSearch }
115
141
/>
116
142
) }
@@ -121,3 +147,70 @@ export const SideBarItems = ({
121
147
</ >
122
148
) ;
123
149
} ;
150
+
151
+ const HighlightedText = ( {
152
+ search,
153
+ label,
154
+ } : {
155
+ search : string ;
156
+ label : string ;
157
+ } ) => {
158
+ const chunks = highlightWords ?.( {
159
+ matchExactly : false ,
160
+ query : search ,
161
+ text : label ?. toString ( ) as string ,
162
+ } ) ;
163
+ if ( chunks ?. length > 1 || chunks ?. [ 0 ] ?. match ) {
164
+ return (
165
+ < span aria-label = { label } role = "note" >
166
+ { chunks ?. map ( ( { key, match, text } ) => (
167
+ < Box
168
+ aria-hidden = "true"
169
+ component = "span"
170
+ key = { key }
171
+ sx = {
172
+ match
173
+ ? ( theme ) => ( {
174
+ backgroundColor : theme . palette . warning . light ,
175
+ borderRadius : '2px' ,
176
+ color : ( theme ) =>
177
+ theme . palette . mode === 'dark'
178
+ ? theme . palette . common . black
179
+ : theme . palette . common . white ,
180
+ padding : '2px 1px' ,
181
+ } )
182
+ : undefined
183
+ }
184
+ >
185
+ { text }
186
+ </ Box >
187
+ ) ) }
188
+ </ span >
189
+ ) ;
190
+ }
191
+ return label ;
192
+ } ;
193
+
194
+ const HighlightedKeywords = ( {
195
+ keywords,
196
+ search,
197
+ } : {
198
+ keywords : string [ ] | undefined ;
199
+ search : string ;
200
+ } ) => {
201
+ if ( ! keywords ?. length || ! search ) return null ;
202
+ return (
203
+ < Box
204
+ component = "div"
205
+ sx = { {
206
+ display : 'block' ,
207
+ mt : '12px' ,
208
+ color : 'text.secondary' ,
209
+ fontSize : '0.8rem' ,
210
+ fontStyle : 'italic' ,
211
+ } }
212
+ >
213
+ (< HighlightedText label = { keywords . join ( ', ' ) } search = { search } /> )
214
+ </ Box >
215
+ ) ;
216
+ } ;
0 commit comments