@@ -15,7 +15,7 @@ import { DataViewTh, DataViewTr, isDataViewTdObject, isDataViewTrObject } from '
1515import { DataViewState } from '../DataView/DataView' ;
1616
1717export interface ExpandableContent {
18- rowId : number ;
18+ rowId : string | number ;
1919 columnId : number ;
2020 content : React . ReactNode ;
2121}
@@ -40,6 +40,8 @@ export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | '
4040 isExpandable ?: boolean ;
4141 /** Toggles sticky columns and header */
4242 isSticky ?: boolean ;
43+ /** Property name used as unique row identifier for expansion tracking. When provided, expanded state follows the data through sort/filter/pagination instead of using the array index. Applicable when rows are objects with an identifying property (e.g., `id`). */
44+ indexBy ?: string ;
4345}
4446
4547export const DataViewTableBasic : FC < DataViewTableBasicProps > = ( {
@@ -52,6 +54,7 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
5254 hasResizableColumns,
5355 isExpandable = false ,
5456 isSticky = false ,
57+ indexBy,
5558 ...props
5659} : DataViewTableBasicProps ) => {
5760 const { selection, activeState, isSelectable } = useInternalContext ( ) ;
@@ -60,26 +63,32 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
6063 const activeHeadState = useMemo ( ( ) => activeState ? headStates ?. [ activeState ] : undefined , [ activeState , headStates ] ) ;
6164 const activeBodyState = useMemo ( ( ) => activeState ? bodyStates ?. [ activeState ] : undefined , [ activeState , bodyStates ] ) ;
6265
63- const [ expandedRowsState , setExpandedRowsState ] = useState < Record < number , boolean > > ( { } )
64- const [ expandedColumnIndex , setExpandedColumnIndex ] = useState < Record < number , number > > ( { } )
66+ const [ expandedRowsState , setExpandedRowsState ] = useState < Record < string | number , boolean > > ( { } )
67+ const [ expandedColumnIndex , setExpandedColumnIndex ] = useState < Record < string | number , number > > ( { } )
6568
6669 const tableRef = useRef < HTMLTableElement > ( null ) ;
6770
6871 const needsSeparateTbody = isExpandable ;
6972
7073 const renderedRows = useMemo ( ( ) => rows . map ( ( row , rowIndex ) => {
7174 const rowIsObject = isDataViewTrObject ( row ) ;
72- const isRowExpanded = expandedRowsState [ rowIndex ] || false ;
73- const expandedColIndex = expandedColumnIndex [ rowIndex ] ;
7475
75- // Get the first cell to extract the row ID
76+ // Resolve the key used for expansion state tracking
77+ const rowKey : string | number = indexBy && rowIsObject && indexBy in row
78+ ? String ( ( row as unknown as Record < string , unknown > ) [ indexBy ] )
79+ : rowIndex ;
80+
81+ const isRowExpanded = expandedRowsState [ rowKey ] || false ;
82+ const expandedColIndex = expandedColumnIndex [ rowKey ] ;
83+
84+ // Get the first cell to extract the row ID (used for expandable content matching when indexBy is not set)
7685 const rowData = rowIsObject ? row . row : row ;
7786 const firstCell = rowData [ 0 ] ;
78- const rowId = isDataViewTdObject ( firstCell ) ? ( firstCell as { id ?: number } ) . id : undefined ;
87+ const rowId = isDataViewTdObject ( firstCell ) ? ( firstCell as { id ?: string | number } ) . id : undefined ;
7988
8089 // Find all expandable contents for this row
8190 const rowExpandableContents = isExpandable ? expandedRows ?. filter (
82- ( content ) => content . rowId === rowId
91+ ( content ) => indexBy ? String ( content . rowId ) === String ( rowKey ) : content . rowId === rowId
8392 ) : [ ] ;
8493
8594 const rowContent = (
@@ -100,7 +109,7 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
100109 { ( rowIsObject ? row . row : row ) . map ( ( cell , colIndex ) => {
101110 const cellIsObject = isDataViewTdObject ( cell ) ;
102111 const cellExpandableContent = isExpandable ? expandedRows ?. find (
103- ( content ) => content . rowId === rowId && content . columnId === colIndex
112+ ( content ) => ( indexBy ? String ( content . rowId ) === String ( rowKey ) : content . rowId === rowId ) && content . columnId === colIndex
104113 ) : undefined ;
105114 return (
106115 < Td
@@ -109,14 +118,14 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
109118 { ...( cellExpandableContent != null && {
110119 compoundExpand : {
111120 isExpanded : isRowExpanded && expandedColIndex === colIndex ,
112- expandId : `expandable-${ rowIndex } ` ,
121+ expandId : `expandable-${ rowKey } ` ,
113122 onToggle : ( ) => {
114123 setExpandedRowsState ( prev => {
115124 const isSameColumn = expandedColIndex === colIndex ;
116- const wasExpanded = prev [ rowIndex ] ;
117- return { ...prev , [ rowIndex ] : isSameColumn ? ! wasExpanded : true } ;
125+ const wasExpanded = prev [ rowKey ] ;
126+ return { ...prev , [ rowKey ] : isSameColumn ? ! wasExpanded : true } ;
118127 } ) ;
119- setExpandedColumnIndex ( prev => ( { ...prev , [ rowIndex ] : colIndex } ) ) ;
128+ setExpandedColumnIndex ( prev => ( { ...prev , [ rowKey ] : colIndex } ) ) ;
120129 } ,
121130 rowIndex,
122131 columnIndex : colIndex
@@ -149,7 +158,7 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
149158 } else {
150159 return rowContent ;
151160 }
152- } ) , [ rows , isSelectable , isSelected , isSelectDisabled , onSelect , ouiaId , expandedRowsState , expandedColumnIndex , expandedRows , isExpandable , needsSeparateTbody ] ) ;
161+ } ) , [ rows , isSelectable , isSelected , isSelectDisabled , onSelect , ouiaId , expandedRowsState , expandedColumnIndex , expandedRows , isExpandable , needsSeparateTbody , indexBy ] ) ;
153162
154163 const bodyContent = activeBodyState || ( needsSeparateTbody ? renderedRows : < Tbody > { renderedRows } </ Tbody > ) ;
155164
0 commit comments