11import React from "react" ;
22import "./DataTable.style.scss"
33import { Component , mergeComponentProps } from "../../utils" ;
4+ import { Flex } from "../flex/Flex" ;
5+ import { DataTableHeader } from "./DataTableHeader" ;
6+ import { DataTablePaginationContext } from "./DataTablePagination" ;
47
58export type DataTableFilterOperator = "isOneOf" | "isNotOneOf"
69
@@ -15,15 +18,21 @@ export interface DataTableSortProps {
1518 [ key : string ] : 'asc' | 'desc' | undefined
1619}
1720
21+ export type DataTableRowRenderer < T > = ( item : T , index : number ) => React . ReactNode
22+
1823export interface DataTableProps < T > extends Omit < Component < HTMLTableElement > , 'data' | 'children' | 'onSelect' > {
1924 data : Array < T >
2025 sort ?: DataTableSortProps
2126 filter ?: DataTableFilterProps
27+ limit ?: number
28+ //when enabled the table splits its rows into pages of `limit` (default 10) and creates the pagination context
29+ pagination ?: boolean
2230 loading ?: boolean
2331 loadingComponent ?: React . ReactNode
2432 emptyComponent ?: React . ReactNode
2533 onSelect ?: ( item : T | undefined ) => void
26- children ?: ( item : T , index : number ) => React . ReactNode
34+ //the row renderer (function child) plus an optional <DataTableHeader/> and any pagination controls, e.g. <DataTablePagination/>, to render below the table
35+ children ?: DataTableRowRenderer < T > | React . ReactNode | Array < DataTableRowRenderer < T > | React . ReactNode >
2736}
2837
2938const getNestedValue = ( obj : any , path : string ) : any => {
@@ -41,7 +50,27 @@ const getNestedValue = (obj: any, path: string): any => {
4150
4251export const DataTable = < T , > ( props : DataTableProps < T > ) => {
4352
44- const { data, sort, filter, loading, loadingComponent, emptyComponent, onSelect, children, ...rest } = props
53+ const {
54+ data,
55+ sort,
56+ filter,
57+ limit,
58+ pagination,
59+ loading,
60+ loadingComponent,
61+ emptyComponent,
62+ onSelect,
63+ children,
64+ ...rest
65+ } = props
66+
67+ const [ page , setPage ] = React . useState ( 0 )
68+
69+ // children may hold the row renderer (a function), an optional <DataTableHeader/> and pagination controls (elements)
70+ const childArray : Array < DataTableRowRenderer < T > | React . ReactNode > = Array . isArray ( children ) ? children : [ children ]
71+ const renderRow = childArray . find ( ( child ) : child is DataTableRowRenderer < T > => typeof child === "function" )
72+ const headerNode = childArray . find ( ( child ) => React . isValidElement ( child ) && child . type === DataTableHeader ) as React . ReactNode
73+ const footerNodes = childArray . filter ( ( child ) => typeof child !== "function" && child !== headerNode ) as React . ReactNode [ ]
4574
4675 const filteredData = data . filter ( item => {
4776 return Object . entries ( filter || { } ) . every ( ( [ key , { operator, value} ] ) => {
@@ -84,17 +113,44 @@ export const DataTable = <T, >(props: DataTableProps<T>) => {
84113 } )
85114 } , [ filteredData , sort ] )
86115
116+ const pageSize = typeof limit === "number" ? limit : 10
117+ const pageCount = pagination ? Math . max ( 1 , Math . ceil ( sortedData . length / pageSize ) ) : 1
118+ // clamp: filtering/sorting can shrink the data below the current page
119+ const currentPage = Math . min ( page , pageCount - 1 )
120+
121+ React . useEffect ( ( ) => {
122+ if ( page !== currentPage ) setPage ( currentPage )
123+ } , [ page , currentPage ] )
124+
125+ const visibleData = pagination
126+ ? sortedData . slice ( currentPage * pageSize , currentPage * pageSize + pageSize )
127+ : typeof limit === "number" ? sortedData . slice ( 0 , limit ) : sortedData
128+
87129 // @ts -ignore
88- return < table { ...mergeComponentProps ( "data-table" , rest ) } >
89- { sortedData . map ( ( item , i ) => {
130+ const table = < table { ...mergeComponentProps ( "data-table" , rest ) } >
131+ { headerNode }
132+ < tbody >
133+ { visibleData . map ( ( item , i ) => {
90134 return < tr className = { "data-table__row" } onClick = { ( ) => onSelect ?.( item ) } >
91- { children ?.( item , i ) }
135+ { renderRow ?.( item , i ) }
92136 </ tr >
93137 } ) }
94138 { sortedData . length === 0 && ! loading && emptyComponent ? (
95139 < tr className = { "data-table__row" } onClick = { ( ) => onSelect ?.( undefined ) } >
96140 { emptyComponent }
97141 </ tr >
98142 ) : null }
143+ </ tbody >
99144 </ table >
145+
146+ if ( ! pagination ) return table
147+
148+ // DataTable owns the pagination state and provides the context that the controls
149+ // (e.g. a <DataTablePagination/> placed as a child) read from.
150+ return < DataTablePaginationContext . Provider value = { { page : currentPage , pageCount, setPage} } >
151+ < Flex style = { { flexDirection : "column" , gap : "0.5rem" } } >
152+ { table }
153+ { footerNodes . map ( ( node , i ) => < React . Fragment key = { i } > { node } </ React . Fragment > ) }
154+ </ Flex >
155+ </ DataTablePaginationContext . Provider >
100156}
0 commit comments