88 type ComponentPropsWithoutRef ,
99 type ReactElement ,
1010 type ReactNode ,
11- type RefObject ,
1211 useCallback ,
1312 useContext ,
1413 useMemo ,
@@ -29,8 +28,18 @@ import {
2928 Locale ,
3029 type NestedStrings ,
3130} from '@edgeandnode/gds'
31+ import {
32+ BookOpenText ,
33+ Files ,
34+ House ,
35+ RoleIndexer ,
36+ Stack ,
37+ Subgraph ,
38+ Substreams ,
39+ SubstreamsPoweredSubgraph ,
40+ TheGraph ,
41+ } from '@edgeandnode/gds/icons'
3242
33- import { TheGraphLogo } from '@/assets/TheGraphLogo'
3443import {
3544 Difficulty ,
3645 DocSearch ,
@@ -203,24 +212,50 @@ export default function NextraLayout({ children, pageOpts, pageProps }: NextraTh
203212 return result
204213 } , [ fsPath , pageMap , locale ] )
205214
215+ const activeRoute = normalizePagesResult . activePath . at ( - 1 ) ?. route ?? null
216+
206217 type NavigationItem = WithOptional < ( typeof normalizePagesResult . directories ) [ number ] , 'children' >
207218 type NavigationGroup = {
208219 title : string | undefined
220+ icon : ReactNode | undefined
209221 route : string | undefined
210222 items : NavigationItem [ ]
211223 }
212- const navigationItemIsExpandable = ( item : NavigationItem ) => {
213- if ( ! item . children ?. length ) return false
214- switch ( item . type ) {
215- case 'children' :
216- /**
217- * This is hacky: we want to render an item of type `children` without a `NavigationItem` wrapper only when
218- * it doesn't have a title, but Nextra defaults `title` to `name` so it's never actually empty.
219- */
220- return item . title !== item . name
221- default :
222- return true
224+ const getNavigationItemIcon = ( item : NavigationItem ) => {
225+ const routeWithoutLocale = item . route . slice ( 3 ) || '/'
226+ if ( routeWithoutLocale === '/' ) {
227+ return < House variant = "fill" alt = "" />
228+ }
229+ if ( routeWithoutLocale === '/about' || routeWithoutLocale . startsWith ( '/about/' ) ) {
230+ return < TheGraph alt = "" />
231+ }
232+ if ( routeWithoutLocale === '/supported-networks' || routeWithoutLocale . startsWith ( '/supported-networks/' ) ) {
233+ return < Stack alt = "" />
234+ }
235+ if ( routeWithoutLocale === '/contracts' || routeWithoutLocale . startsWith ( '/contracts/' ) ) {
236+ return < Files alt = "" />
237+ }
238+ if ( routeWithoutLocale === '/subgraphs' || routeWithoutLocale . startsWith ( '/subgraphs/' ) ) {
239+ return < Subgraph alt = "" />
240+ }
241+ if ( routeWithoutLocale === '/substreams' || routeWithoutLocale . startsWith ( '/substreams/' ) ) {
242+ return < Substreams alt = "" />
243+ }
244+ if ( routeWithoutLocale === '/sps' || routeWithoutLocale . startsWith ( '/sps/' ) ) {
245+ return < SubstreamsPoweredSubgraph alt = "" />
223246 }
247+ if ( routeWithoutLocale === '/indexing' || routeWithoutLocale . startsWith ( '/indexing/' ) ) {
248+ return < RoleIndexer alt = "" />
249+ }
250+ if (
251+ routeWithoutLocale === '/resources' ||
252+ routeWithoutLocale . startsWith ( '/resources/' ) ||
253+ routeWithoutLocale === '/archived' ||
254+ routeWithoutLocale . startsWith ( '/archived/' )
255+ ) {
256+ return < BookOpenText alt = "" />
257+ }
258+ return null
224259 }
225260 const getNavigationItemRoute = ( item : NavigationItem ) => {
226261 let currentItem = item
@@ -237,33 +272,55 @@ export default function NextraLayout({ children, pageOpts, pageProps }: NextraTh
237272 }
238273 const navigationGroups = normalizePagesResult . directories . reduce < NavigationGroup [ ] > (
239274 ( groups , item : NavigationItem ) => {
240- if ( item . type === 'separator' ) {
241- groups . push ( { title : item . title , route : undefined , items : [ ] } )
242- } else {
275+ const itemHasChildren = Boolean ( item . children ?. length )
276+ if ( item . type === 'children' && ! itemHasChildren ) return groups
277+ /**
278+ * When an item of type `children` doesn't have a title set in the meta file, we want to render it without a `NavigationItem` wrapper.
279+ * Unfortunately, Nextra defaults `title` to `name`, so the way to check if it's empty is to check if it's equal to `name`, which should
280+ * never happen with an explicitly set `title` because it should be title case whereas `name` will always be lowercase.
281+ */
282+ const itemHasNoWrapper = item . type === 'children' && item . title === item . name
283+ let currentGroup = groups [ groups . length - 1 ]
284+ /**
285+ * Create a new group if it's the first one, if the current item is a separator, or if the current group has some items but no title
286+ * (meaning its items are rendered at the top level instead of in an expandable panel) while the current item has children and a wrapper.
287+ */
288+ if (
289+ ! currentGroup ||
290+ item . type === 'separator' ||
291+ ( currentGroup . title === undefined && currentGroup . items . length > 0 && itemHasChildren && ! itemHasNoWrapper )
292+ ) {
293+ groups . push ( {
294+ title : item . type === 'separator' ? item . title : undefined ,
295+ icon : undefined ,
296+ route : undefined ,
297+ items : [ ] ,
298+ } )
299+ }
300+ if ( item . type === 'separator' ) return groups
301+ currentGroup = groups [ groups . length - 1 ] !
302+ if ( currentGroup . title === undefined && currentGroup . items . length === 0 && itemHasChildren && ! itemHasNoWrapper ) {
303+ /**
304+ * If the group has no title and the first item we want to add to it has children and a wrapper, give the item's title, icon, route
305+ * (if different from the first child's), and children to the group, instead of adding the item itself.
306+ */
307+ currentGroup . title = item . title
308+ currentGroup . icon = getNavigationItemIcon ( item )
243309 const route = getNavigationItemRoute ( item )
244- let currentGroup = groups [ groups . length - 1 ]
245- if (
246- ! currentGroup ||
247- currentGroup . items . some ( navigationItemIsExpandable ) ||
248- ( currentGroup . title === undefined && navigationItemIsExpandable ( item ) )
249- ) {
250- groups . push ( {
251- title : undefined ,
252- route,
253- items : [ ] ,
254- } )
255- }
256- currentGroup = groups [ groups . length - 1 ] !
310+ const firstChildRoute = getNavigationItemRoute ( item . children ! [ 0 ] ! )
311+ currentGroup . route = route !== firstChildRoute ? route : undefined
312+ currentGroup . items . push ( ...item . children ! )
313+ } else if ( itemHasNoWrapper ) {
314+ // Otherwise, if the first or next item to add to the group has no wrapper, just add its children to it
315+ currentGroup . items . push ( ...( item . children ?? [ ] ) )
316+ } else {
317+ // Otherwise, add the item (which may or may not have children) to the group
257318 currentGroup . items . push ( item )
258- if ( currentGroup . route === undefined && route !== undefined ) {
259- currentGroup . route = route
260- }
261319 }
262320 return groups
263321 } ,
264322 [ ] ,
265323 )
266- const activeRoute = normalizePagesResult . activePath . at ( - 1 ) ?. route ?? null
267324
268325 // Provide `markOutlineItem` to the `DocumentContext` so child `Heading` components can mark outline items as "in or above view" or not
269326 const [
@@ -324,7 +381,7 @@ export default function NextraLayout({ children, pageOpts, pageProps }: NextraTh
324381 >
325382 < header className = "flex h-16 items-center border-b border-white/8 pe-4 ps-6" >
326383 < div >
327- < TheGraphLogo alt = "" size = { 8 } />
384+ < TheGraph alt = "" size = { 8 } />
328385 < ButtonOrLink href = "https://thegraph.com" className = "absolute -inset-2" >
329386 < span className = "sr-only" > The Graph</ span >
330387 </ ButtonOrLink >
@@ -382,33 +439,33 @@ export default function NextraLayout({ children, pageOpts, pageProps }: NextraTh
382439 </ div >
383440 ) }
384441 </ DocSearch >
385- { /* TODO: Add icons to some navigation items */ }
386442 { navigationGroups . map ( ( group , groupIndex ) => {
387443 if ( group . items . length === 0 ) {
388444 return null
389445 }
390446 const groupContent = group . items . map ( ( groupItem ) =>
391- ( function renderItem ( item : typeof groupItem ) : ReactNode {
392- const childrenContent = item . children ?. length ? item . children . map ( renderItem ) : null
393- if ( childrenContent && ! navigationItemIsExpandable ( item ) ) {
394- return childrenContent
395- } else {
396- return (
397- < NavigationItem
398- key = { item . name }
399- title = { item . title }
400- href = { getNavigationItemRoute ( item ) }
401- selected = { item . route === activeRoute }
402- children = { childrenContent }
403- />
404- )
405- }
447+ ( function renderItem ( item : typeof groupItem ) {
448+ return (
449+ < NavigationItem
450+ key = { item . name }
451+ title = { item . title }
452+ icon = { getNavigationItemIcon ( item ) }
453+ href = { getNavigationItemRoute ( item ) }
454+ selected = { item . route === activeRoute }
455+ children = { item . children ?. length ? < > { item . children . map ( renderItem ) } </ > : undefined }
456+ />
457+ )
406458 } ) ( groupItem ) ,
407459 )
408460 return (
409461 < NavigationGroup key = { groupIndex } >
410462 { group . title !== undefined ? (
411- < NavigationItem title = { group . title } href = { group . route } >
463+ < NavigationItem
464+ title = { group . title }
465+ icon = { group . icon ?? getNavigationItemIcon ( group . items [ 0 ] ! ) }
466+ href = { group . route ?? getNavigationItemRoute ( group . items [ 0 ] ! ) }
467+ selected = { group . route === activeRoute }
468+ >
412469 { groupContent }
413470 </ NavigationItem >
414471 ) : (
0 commit comments