@@ -22,11 +22,14 @@ import { useEffectAfterMount } from '@Common/Helper'
2222import { Pagination } from '@Common/Pagination'
2323import { SortableTableHeaderCell } from '@Common/SortableTableHeaderCell'
2424import { CHECKBOX_VALUE } from '@Common/Types'
25+ import { Button , ButtonStyleType , ButtonVariantType } from '@Shared/Components/Button'
26+ import { Icon } from '@Shared/Components/Icon'
27+ import { ComponentSizeType } from '@Shared/constants'
2528
2629import { BulkSelection } from '../BulkSelection'
2730import BulkSelectionActionWidget from './BulkSelectionActionWidget'
2831import { BULK_ACTION_GUTTER_LABEL , EVENT_TARGET , SHIMMER_DUMMY_ARRAY } from './constants'
29- import { BulkActionStateType , FiltersTypeEnum , PaginationEnum , SignalsType , TableContentProps } from './types'
32+ import { BulkActionStateType , FiltersTypeEnum , PaginationEnum , RowType , SignalsType , TableContentProps } from './types'
3033import useTableWithKeyboardShortcuts from './useTableWithKeyboardShortcuts'
3134import { getStickyColumnConfig , scrollToShowActiveElementIfNeeded } from './utils'
3235
@@ -61,6 +64,7 @@ const TableContent = <
6164
6265 const [ bulkActionState , setBulkActionState ] = useState < BulkActionStateType > ( null )
6366 const [ showBorderRightOnStickyElements , setShowBorderRightOnStickyElements ] = useState ( false )
67+ const [ expandState , setExpandState ] = useState < Record < string , boolean > > ( { } )
6468
6569 const { width : rowOnHoverComponentWidth , Component : RowOnHoverComponent } = rowActionOnHoverConfig || { }
6670
@@ -92,10 +96,50 @@ const TableContent = <
9296 . join ( ' ' ) ,
9397 } = resizableConfig ?? { }
9498
95- const gridTemplateColumns = rowOnHoverComponentWidth
99+ const { visibleRows, areAllRowsExpanded, isAnyRowExpandable } = useMemo ( ( ) => {
100+ const normalizedFilteredRows = filteredRows ?? [ ]
101+
102+ const paginatedRows =
103+ paginationVariant !== PaginationEnum . PAGINATED ||
104+ ( paginationVariant === PaginationEnum . PAGINATED && getRows )
105+ ? normalizedFilteredRows
106+ : normalizedFilteredRows . slice ( offset , offset + pageSize )
107+
108+ const _isAnyRowExpandable = paginatedRows . some ( ( row ) => ! ! row . expandableRows )
109+
110+ const _areAllRowsExpanded =
111+ _isAnyRowExpandable &&
112+ paginatedRows . reduce ( ( acc , row ) => {
113+ if ( row . expandableRows ) {
114+ return acc && ! ! expandState [ row . id ]
115+ }
116+
117+ return acc
118+ } , true )
119+
120+ const paginatedRowsWithExpandedRows = paginatedRows . flatMap ( ( row ) => {
121+ if ( row . expandableRows && expandState [ row . id ] ) {
122+ return [ row , ...row . expandableRows ]
123+ }
124+
125+ return [ row ]
126+ } )
127+
128+ return {
129+ visibleRows : paginatedRowsWithExpandedRows ,
130+ areAllRowsExpanded : _areAllRowsExpanded ,
131+ isAnyRowExpandable : _isAnyRowExpandable ,
132+ }
133+ } , [ paginationVariant , offset , pageSize , filteredRows , expandState ] )
134+
135+ const gridTemplateColumnsWithoutExpandButton = rowOnHoverComponentWidth
96136 ? `${ initialGridTemplateColumns } ${ typeof rowOnHoverComponentWidth === 'number' ? `minmax(${ rowOnHoverComponentWidth } px, 1fr)` : rowOnHoverComponentWidth } `
97137 : initialGridTemplateColumns
98138
139+ const gridTemplateColumns = isAnyRowExpandable
140+ ? `16px ${ gridTemplateColumnsWithoutExpandButton } `
141+ : gridTemplateColumnsWithoutExpandButton
142+
99143 useEffect ( ( ) => {
100144 const scrollEventHandler = ( ) => {
101145 setShowBorderRightOnStickyElements ( rowsContainerRef . current ?. scrollLeft > 0 )
@@ -113,18 +157,6 @@ const TableContent = <
113157
114158 const bulkSelectionCount = isBulkSelectionApplied ? totalRows : ( getSelectedIdentifiersCount ?.( ) ?? 0 )
115159
116- const visibleRows = useMemo ( ( ) => {
117- const normalizedFilteredRows = filteredRows ?? [ ]
118-
119- const paginatedRows =
120- paginationVariant !== PaginationEnum . PAGINATED ||
121- ( paginationVariant === PaginationEnum . PAGINATED && getRows )
122- ? normalizedFilteredRows
123- : normalizedFilteredRows . slice ( offset , offset + pageSize )
124-
125- return paginatedRows
126- } , [ paginationVariant , offset , pageSize , filteredRows ] )
127-
128160 const isBEPagination = ! ! getRows
129161
130162 const showPagination =
@@ -155,6 +187,18 @@ const TableContent = <
155187 handleSorting ( newSortBy )
156188 }
157189
190+ const toggleExpandAll = ( ) => {
191+ setExpandState (
192+ visibleRows . reduce ( ( acc , row ) => {
193+ if ( ( row as RowType < RowData > ) . expandableRows ) {
194+ acc [ row . id ] = ! areAllRowsExpanded
195+ }
196+
197+ return acc
198+ } , { } ) ,
199+ )
200+ }
201+
158202 const focusActiveRow = ( node : HTMLDivElement ) => {
159203 if (
160204 node &&
@@ -216,6 +260,7 @@ const TableContent = <
216260 return visibleRows . map ( ( row , visibleRowIndex ) => {
217261 const isRowActive = activeRowIndex === visibleRowIndex
218262 const isRowBulkSelected = ! ! bulkSelectionState [ row . id ] || isBulkSelectionApplied
263+ const isExpandedRow = row . id . startsWith ( 'expanded-row' )
219264
220265 const handleChangeActiveRowIndex = ( ) => {
221266 setActiveRowIndex ( visibleRowIndex )
@@ -225,6 +270,13 @@ const TableContent = <
225270 handleToggleBulkSelectionOnRow ( row )
226271 }
227272
273+ const toggleExpandRow = ( ) => {
274+ setExpandState ( {
275+ ...expandState ,
276+ [ row . id ] : ! expandState [ row . id ] ,
277+ } )
278+ }
279+
228280 return (
229281 < div
230282 key = { row . id }
@@ -236,22 +288,37 @@ const TableContent = <
236288 isRowActive ? 'generic-table__row--active checkbox__parent-container--active' : ''
237289 } ${ rowActionOnHoverConfig ? 'dc__opacity-hover dc__opacity-hover--parent' : '' } ${
238290 isRowBulkSelected ? 'generic-table__row--bulk-selected' : ''
239- } `}
291+ } ${ isExpandedRow ? 'generic-table__row--expanded-row' : '' } `}
240292 style = { {
241293 gridTemplateColumns,
242294 } }
243295 data-active = { isRowActive }
244296 // NOTE: by giving it a negative tabIndex we can programmatically focus it through .focus()
245297 tabIndex = { - 1 }
246298 >
299+ { ! isExpandedRow && ! ! ( row as RowType < RowData > ) . expandableRows ? (
300+ < Button
301+ dataTestId = { `expand-row-${ row . id } ` }
302+ icon = { < Icon name = "ic-expand-right-sm" color = { null } /> }
303+ ariaLabel = "Expand row"
304+ showAriaLabelInTippy = { false }
305+ variant = { ButtonVariantType . borderLess }
306+ size = { ComponentSizeType . xs }
307+ style = { ButtonStyleType . neutral }
308+ onClick = { toggleExpandRow }
309+ />
310+ ) : null }
311+
312+ { isAnyRowExpandable && ( isExpandedRow || ! ( row as RowType < RowData > ) . expandableRows ) && < div /> }
313+
247314 { visibleColumns . map ( ( { field, horizontallySticky : isStickyColumn , CellComponent } , index ) => {
248315 const isBulkActionGutter = field === BULK_ACTION_GUTTER_LABEL
249316 const horizontallySticky = isStickyColumn || isBulkActionGutter
250317 const { className : stickyClassName = '' , left : stickyLeftValue = '' } = horizontallySticky
251318 ? getStickyColumnConfig ( gridTemplateColumns , index )
252319 : { }
253320
254- if ( isBulkActionGutter ) {
321+ if ( isBulkActionGutter && ! isExpandedRow ) {
255322 return (
256323 < div
257324 className = { `flexbox dc__align-items-center ${ stickyClassName } ` }
@@ -282,6 +349,8 @@ const TableContent = <
282349 row = { row }
283350 filterData = { filterData as any }
284351 isRowActive = { isRowActive }
352+ isExpandedRow = { isExpandedRow }
353+ isRowInExpandState = { expandState [ row . id ] }
285354 { ...additionalProps }
286355 />
287356 ) : (
@@ -295,7 +364,7 @@ const TableContent = <
295364 )
296365 } ) }
297366
298- { RowOnHoverComponent && (
367+ { ! isExpandedRow && RowOnHoverComponent && (
299368 < div
300369 className = { `dc__position-sticky dc__right-0 dc__zi-1 ${ ! isRowActive ? 'dc__opacity-hover--child' : '' } ` }
301370 >
@@ -337,6 +406,19 @@ const TableContent = <
337406 gridTemplateColumns,
338407 } }
339408 >
409+ { isAnyRowExpandable ? (
410+ < Button
411+ dataTestId = "expand-all-rows"
412+ icon = { < Icon name = "ic-expand-right-sm" color = { null } /> }
413+ ariaLabel = "Expand row"
414+ showAriaLabelInTippy = { false }
415+ variant = { ButtonVariantType . borderLess }
416+ size = { ComponentSizeType . xs }
417+ style = { ButtonStyleType . neutral }
418+ onClick = { toggleExpandAll }
419+ />
420+ ) : null }
421+
340422 { visibleColumns . map (
341423 (
342424 {
0 commit comments