@@ -19,6 +19,8 @@ export type TableOfContentsItem = {
1919 icon ?: FAIconProp ;
2020 activeIcon ?: FAIconProp ;
2121 iconColor ?: string ;
22+ iconPosition ?: 'left' | 'right' ;
23+ textIcon ?: string ;
2224 isLoading ?: boolean ;
2325 isDisabled ?: boolean ;
2426 showSkeleton ?: boolean ;
@@ -107,13 +109,24 @@ function TableOfContentsInner<T extends TableOfContentsItem = TableOfContentsIte
107109
108110 // an array of functions. Invoking the N-th function toggles the expanded flag on the N-th content item
109111 const toggleExpandedFunctions = React . useMemo ( ( ) => {
110- return range ( contents . length ) . map ( i => ( ) =>
111- setExpanded ( current => ( {
112- ...current ,
113- [ i ] : ! current [ i ] ,
114- } ) ) ,
115- ) ;
116- } , [ contents . length ] ) ;
112+ return range ( contents . length ) . map ( i => ( ) => {
113+ setExpanded ( current => {
114+ let childrenToCollapse = { } ;
115+ if ( current [ i ] ) {
116+ const item = contents [ i ] ;
117+ const children = findDescendantIndices ( item . depth ?? 0 , i , contents . slice ( i + 1 ) ) ;
118+ childrenToCollapse = Object . fromEntries ( children . map ( i => [ i , false ] ) ) ;
119+ }
120+
121+ return {
122+ ...current ,
123+ [ i ] : ! current [ i ] ,
124+ ...childrenToCollapse ,
125+ } ;
126+ } ) ;
127+ } ) ;
128+ // eslint-disable-next-line react-hooks/exhaustive-deps
129+ } , [ contents , contents . length ] ) ;
117130
118131 // expand ancestors of active items by default
119132 React . useEffect ( ( ) => {
@@ -203,6 +216,7 @@ export function TableOfContents<T extends TableOfContentsItem = TableOfContentsI
203216
204217function DefaultRowImpl < T extends TableOfContentsItem > ( { item, isExpanded, toggleExpanded } : RowComponentProps < T > ) {
205218 const isGroup = item . type === 'group' ;
219+ const isGroupItem = isGroup && isTableOfContentsLink ( item ) ;
206220 const isChild = item . type !== 'group' && ( item . depth ?? 0 ) > 0 ;
207221 const isDivider = item . type === 'divider' ;
208222 const showSkeleton = item . showSkeleton ;
@@ -239,7 +253,7 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
239253 return ;
240254 }
241255
242- if ( ! isGroup ) return ;
256+ if ( ! isGroup || isGroupItem ) return ;
243257
244258 e . preventDefault ( ) ;
245259 toggleExpanded ( ) ;
@@ -290,6 +304,27 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
290304 />
291305 ) : null ;
292306
307+ const iconElem = icon ? (
308+ < FAIcon
309+ className = { cn ( 'fa-fw' , {
310+ 'mr-3' : item . iconPosition !== 'right' ,
311+ 'mx-1' : item . iconPosition === 'right' ,
312+ 'text-blue-6' : isSelected ,
313+ [ `text-${ item . iconColor } ` ] : item . iconColor ,
314+ 'bp3-skeleton' : item . showSkeleton ,
315+ } ) }
316+ icon = { icon }
317+ />
318+ ) : item . textIcon ? (
319+ < div
320+ className = { cn ( 'text-right rounded px-1 text-xs uppercase' , {
321+ [ `text-${ item . iconColor } ` ] : item . iconColor ,
322+ } ) }
323+ >
324+ { item . textIcon }
325+ </ div >
326+ ) : null ;
327+
293328 return (
294329 < div
295330 onClick = { onClick }
@@ -299,12 +334,7 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
299334 >
300335 < div className = { cn ( '-ml-px' , innerClassName , { 'opacity-75' : isDisabled } ) } >
301336 < div className = "flex flex-row items-center" >
302- { icon && (
303- < FAIcon
304- className = { cn ( 'mr-3 fa-fw' , { 'text-blue-6' : isSelected , 'bp3-skeleton' : item . showSkeleton } ) }
305- icon = { icon }
306- />
307- ) }
337+ { item . iconPosition !== 'right' && iconElem }
308338
309339 < span className = { cn ( 'TableOfContentsItem__name flex-1 truncate' , { 'bp3-skeleton' : item . showSkeleton } ) } >
310340 { item . name }
@@ -313,11 +343,11 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
313343 { item . meta && < span className = "text-sm text-left text-gray font-medium" > { item . meta } </ span > }
314344 { loadingElem }
315345 { actionElem }
346+ { item . iconPosition === 'right' && iconElem }
316347 { isGroup && (
317- < FAIcon
318- className = "TableOfContentsItem__icon"
319- icon = { [ 'far' , isExpanded ? 'chevron-down' : 'chevron-right' ] }
320- />
348+ < div onClick = { isGroupItem ? toggleExpanded : undefined } className = "px-2" >
349+ < FAIcon className = "TableOfContentsItem__icon" icon = { isExpanded ? 'chevron-down' : 'chevron-right' } />
350+ </ div >
321351 ) }
322352 </ div >
323353 { item . footer }
@@ -354,6 +384,23 @@ function findAncestorIndices(currentDepth: number, precedingContents: TableOfCon
354384 ] ;
355385}
356386
387+ function findDescendantIndices (
388+ currentDepth : number ,
389+ currentIndex : number ,
390+ succeedingContents : TableOfContentsItem [ ] ,
391+ ) : number [ ] {
392+ const children : number [ ] = [ ] ;
393+ for ( let index = 0 ; index < succeedingContents . length ; index ++ ) {
394+ if ( ( succeedingContents [ index ] . depth ?? 0 ) <= currentDepth ) {
395+ break ;
396+ } else {
397+ children . push ( currentIndex + index ) ;
398+ }
399+ }
400+
401+ return children ;
402+ }
403+
357404function isExternalLink ( item : TableOfContentsItem ) : boolean {
358405 return isTableOfContentsLink ( item ) && item . to !== void 0 && / ^ ( h t t p | # | m a i l t o ) / . test ( item . to ) ;
359406}
0 commit comments