@@ -8,19 +8,18 @@ import {
88 getSortedRowModel ,
99 SortingState ,
1010 useReactTable ,
11+ ExpandedState ,
12+ getExpandedRowModel ,
1113} from "@tanstack/react-table" ;
1214import React , { useEffect , useMemo , useRef , useState } from "react" ;
1315import { Col , Container , Row , Table as BTable } from "react-bootstrap" ;
16+ import { BsChevronRight , BsChevronDown } from "react-icons/bs" ;
1417import ColumnFilter from "./ColumnFilter" ;
1518import GlobalFilter from "./GlobalFilter" ;
1619import Pagination from "./Pagination" ;
1720import RowSelectCheckBox from "./RowSelectCheckBox" ;
1821import { FaSearch } from "react-icons/fa" ;
1922
20- /**
21- * @author Ankur Mundra on May, 2023
22- */
23-
2423interface TableProps {
2524 data : Record < string , any > [ ] ;
2625 columns : ColumnDef < any , any > [ ] ;
@@ -30,6 +29,9 @@ interface TableProps {
3029 tableSize ?: { span : number ; offset : number } ;
3130 columnVisibility ?: Record < string , boolean > ;
3231 onSelectionChange ?: ( selectedData : Record < any , any > [ ] ) => void ;
32+ renderSubComponent ?: ( props : { row : any } ) => React . ReactNode ;
33+ getRowCanExpand ?: ( row : any ) => boolean ;
34+ disableGlobalFilter ?: boolean ; // Disable the Global Search
3335}
3436
3537const Table : React . FC < TableProps > = ( {
@@ -41,89 +43,104 @@ const Table: React.FC<TableProps> = ({
4143 onSelectionChange,
4244 columnVisibility = { } ,
4345 tableSize = { span : 12 , offset : 0 } ,
46+ renderSubComponent,
47+ getRowCanExpand,
48+ disableGlobalFilter = false , // // Disable the Global Search
4449} ) => {
45- const colsPlusSelectable = useMemo ( ( ) => {
46- const selectableColumn : any = {
47- id : "select" ,
48- header : ( { table } : any ) => {
49- return (
50- < RowSelectCheckBox
51- { ...{
52- checked : table . getIsAllRowsSelected ( ) ,
53- indeterminate : table . getIsSomeRowsSelected ( ) ,
54- onChange : table . getToggleAllRowsSelectedHandler ( ) ,
55- } }
56- />
57- ) ;
58- } ,
59- cell : ( { row } : any ) => {
60- return (
61- < RowSelectCheckBox
62- { ...{
63- checked : row . getIsSelected ( ) ,
64- disabled : ! row . getCanSelect ( ) ,
65- indeterminate : row . getIsSomeSelected ( ) ,
66- onChange : row . getToggleSelectedHandler ( ) ,
67- } }
68- />
69- ) ;
70- } ,
71- enableSorting : false ,
72- enableFilter : false ,
73- } ;
74- return [ selectableColumn , ...columns ] ;
75- } , [ columns ] ) ;
76-
7750 const [ rowSelection , setRowSelection ] = useState ( { } ) ;
7851 const [ sorting , setSorting ] = useState < SortingState > ( [ ] ) ;
7952 const [ globalFilter , setGlobalFilter ] = useState < string | number > ( "" ) ;
8053 const [ columnFilters , setColumnFilters ] = useState < ColumnFiltersState > ( [ ] ) ;
8154 const [ columnVisibilityState , setColumnVisibilityState ] = useState ( columnVisibility ) ;
82- const [ isGlobalFilterVisible , setIsGlobalFilterVisible ] = useState ( showGlobalFilter ) ; // State for global filter visibility
55+ const [ isGlobalFilterVisible , setIsGlobalFilterVisible ] = useState ( showGlobalFilter ) ;
56+ const [ expanded , setExpanded ] = useState < ExpandedState > ( { } ) ;
8357
8458 const selectable = typeof onSelectionChange === "function" ;
8559 const onSelectionChangeRef = useRef < any > ( onSelectionChange ) ;
8660
61+ const colsPlusExpander = useMemo ( ( ) => {
62+ if ( ! renderSubComponent ) return columns ;
63+
64+ const expanderColumn : ColumnDef < any , any > = {
65+ id : "expander" ,
66+ header : ( ) => null ,
67+ cell : ( { row } ) => {
68+ if ( getRowCanExpand ? ! getRowCanExpand ( row ) : false ) {
69+ return null ;
70+ }
71+ return (
72+ < button
73+ className = "btn btn-link p-0"
74+ onClick = { ( e ) => {
75+ e . stopPropagation ( ) ;
76+ row . toggleExpanded ( ) ;
77+ } }
78+ >
79+ { row . getIsExpanded ( ) ? < BsChevronDown /> : < BsChevronRight /> }
80+ </ button >
81+ ) ;
82+ } ,
83+ enableSorting : false ,
84+ enableColumnFilter : false ,
85+ } ;
86+
87+ const selectableColumn = selectable
88+ ? [
89+ {
90+ id : "select" ,
91+ header : ( { table } : any ) => (
92+ < RowSelectCheckBox
93+ { ...{
94+ checked : table . getIsAllRowsSelected ( ) ,
95+ indeterminate : table . getIsSomeRowsSelected ( ) ,
96+ onChange : table . getToggleAllRowsSelectedHandler ( ) ,
97+ } }
98+ />
99+ ) ,
100+ cell : ( { row } : any ) => (
101+ < RowSelectCheckBox
102+ { ...{
103+ checked : row . getIsSelected ( ) ,
104+ disabled : ! row . getCanSelect ( ) ,
105+ indeterminate : row . getIsSomeSelected ( ) ,
106+ onChange : row . getToggleSelectedHandler ( ) ,
107+ } }
108+ />
109+ ) ,
110+ enableSorting : false ,
111+ enableFilter : false ,
112+ } ,
113+ ]
114+ : [ ] ;
115+
116+ return [ ...selectableColumn , expanderColumn , ...columns ] ;
117+ } , [ columns , selectable , renderSubComponent , getRowCanExpand ] ) ;
118+
87119 const table = useReactTable ( {
88120 data : initialData ,
89- columns : selectable ? colsPlusSelectable : columns ,
121+ columns : colsPlusExpander ,
90122 state : {
91123 sorting,
92124 globalFilter,
93125 columnFilters,
94126 rowSelection,
95127 columnVisibility : columnVisibilityState ,
128+ expanded,
96129 } ,
97130 onSortingChange : setSorting ,
98131 onRowSelectionChange : setRowSelection ,
99132 onGlobalFilterChange : setGlobalFilter ,
100133 onColumnFiltersChange : setColumnFilters ,
101134 onColumnVisibilityChange : setColumnVisibilityState ,
135+ onExpandedChange : setExpanded ,
136+ getRowCanExpand,
102137 getCoreRowModel : getCoreRowModel ( ) ,
103138 getSortedRowModel : getSortedRowModel ( ) ,
104139 getFilteredRowModel : getFilteredRowModel ( ) ,
105140 getPaginationRowModel : getPaginationRowModel ( ) ,
141+ getExpandedRowModel : getExpandedRowModel ( ) ,
106142 } ) ;
107143
108- const {
109- getState,
110- getHeaderGroups,
111- getRowModel,
112- getCanNextPage,
113- getCanPreviousPage,
114- previousPage,
115- nextPage,
116- setPageIndex,
117- setPageSize,
118- getPageCount,
119- } = table ;
120-
121- // Used to return early from useEffect() on mount.
122- const firstRenderRef = useRef ( true ) ;
123- // This useEffect() watches flatRows such that on change it
124- // calls the onSelectionChange() prop. Technically, it calls
125- // the onSelectionChangeRef.current function if it exists.
126-
127144 const flatRows = table . getSelectedRowModel ( ) . flatRows ;
128145
129146 useEffect ( ( ) => {
@@ -144,6 +161,8 @@ const Table: React.FC<TableProps> = ({
144161 setIsGlobalFilterVisible ( ! isGlobalFilterVisible ) ;
145162 } ;
146163
164+ const firstRenderRef = useRef ( true ) ;
165+
147166 return (
148167 < >
149168 < Container >
@@ -153,75 +172,80 @@ const Table: React.FC<TableProps> = ({
153172 < GlobalFilter filterValue = { globalFilter } setFilterValue = { setGlobalFilter } />
154173 ) }
155174 </ Col >
156- < span style = { { marginLeft : "5px" } } onClick = { toggleGlobalFilter } >
157- < FaSearch style = { { cursor : "pointer" } } />
158- { isGlobalFilterVisible ? " Hide" : " Show" }
159- </ span > { " " }
175+ { ! disableGlobalFilter && (
176+ < span style = { { marginLeft : "5px" } } onClick = { toggleGlobalFilter } >
177+ < FaSearch style = { { cursor : "pointer" } } />
178+ { isGlobalFilterVisible ? " Hide" : " Show" }
179+ </ span >
180+ ) }
160181 </ Row >
161182 </ Container >
162183 < Container >
163184 < Row >
164185 < Col md = { tableSize } >
165186 < BTable striped hover responsive size = "sm" >
166187 < thead className = "table-secondary" >
167- { getHeaderGroups ( ) . map ( ( headerGroup ) => (
188+ { table . getHeaderGroups ( ) . map ( ( headerGroup ) => (
168189 < tr key = { headerGroup . id } >
169- { headerGroup . headers . map ( ( header ) => {
170- return (
171- < th key = { header . id } colSpan = { header . colSpan } >
172- { header . isPlaceholder ? null : (
173- < >
174- < div
175- { ...{
176- className : header . column . getCanSort ( )
177- ? "cursor-pointer select-none"
178- : "" ,
179- onClick : header . column . getToggleSortingHandler ( ) ,
180- } }
181- >
182- { flexRender ( header . column . columnDef . header , header . getContext ( ) ) }
183- { {
184- asc : " 🔼" ,
185- desc : " 🔽" ,
186- } [ header . column . getIsSorted ( ) as string ] ?? null }
187- </ div >
188- { showColumnFilter && header . column . getCanFilter ( ) ? (
189- < ColumnFilter column = { header . column } />
190- ) : null }
191- </ >
192- ) }
193- </ th >
194- ) ;
195- } ) }
190+ { headerGroup . headers . map ( ( header ) => (
191+ < th key = { header . id } colSpan = { header . colSpan } >
192+ { header . isPlaceholder ? null : (
193+ < >
194+ < div
195+ { ...{
196+ className : header . column . getCanSort ( )
197+ ? "cursor-pointer select-none"
198+ : "" ,
199+ onClick : header . column . getToggleSortingHandler ( ) ,
200+ } }
201+ >
202+ { flexRender ( header . column . columnDef . header , header . getContext ( ) ) }
203+ { {
204+ asc : " 🔼" ,
205+ desc : " 🔽" ,
206+ } [ header . column . getIsSorted ( ) as string ] ?? null }
207+ </ div >
208+ { showColumnFilter && header . column . getCanFilter ( ) ? (
209+ < ColumnFilter column = { header . column } />
210+ ) : null }
211+ </ >
212+ ) }
213+ </ th >
214+ ) ) }
196215 </ tr >
197216 ) ) }
198217 </ thead >
199218 < tbody >
200- { getRowModel ( ) . rows . map ( ( row ) => {
201- return (
202- < tr key = { row . id } >
203- { row . getVisibleCells ( ) . map ( ( cell ) => {
204- return (
205- < td key = { cell . id } >
206- { flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) }
207- </ td >
208- ) ;
209- } ) }
219+ { table . getRowModel ( ) . rows . map ( ( row ) => (
220+ < React . Fragment key = { row . id } >
221+ < tr >
222+ { row . getVisibleCells ( ) . map ( ( cell ) => (
223+ < td key = { cell . id } >
224+ { flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) }
225+ </ td >
226+ ) ) }
210227 </ tr >
211- ) ;
212- } ) }
228+ { row . getIsExpanded ( ) && renderSubComponent && (
229+ < tr >
230+ < td colSpan = { row . getVisibleCells ( ) . length } >
231+ { renderSubComponent ( { row } ) }
232+ </ td >
233+ </ tr >
234+ ) }
235+ </ React . Fragment >
236+ ) ) }
213237 </ tbody >
214238 </ BTable >
215239 { showPagination && (
216240 < Pagination
217- nextPage = { nextPage }
218- previousPage = { previousPage }
219- canNextPage = { getCanNextPage }
220- canPreviousPage = { getCanPreviousPage }
221- setPageIndex = { setPageIndex }
222- setPageSize = { setPageSize }
223- getPageCount = { getPageCount }
224- getState = { getState }
241+ nextPage = { table . nextPage }
242+ previousPage = { table . previousPage }
243+ canNextPage = { table . getCanNextPage }
244+ canPreviousPage = { table . getCanPreviousPage }
245+ setPageIndex = { table . setPageIndex }
246+ setPageSize = { table . setPageSize }
247+ getPageCount = { table . getPageCount }
248+ getState = { table . getState }
225249 />
226250 ) }
227251 </ Col >
@@ -231,4 +255,4 @@ const Table: React.FC<TableProps> = ({
231255 ) ;
232256} ;
233257
234- export default Table ;
258+ export default Table ;
0 commit comments