@tanstack/react-table 0.0.1-alpha.9 → 8.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +34 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/build/cjs/index.js +114 -0
- package/build/cjs/index.js.map +1 -0
- package/build/esm/index.js +99 -0
- package/build/esm/index.js.map +1 -0
- package/build/stats-html.html +2689 -0
- package/build/stats-react.json +99 -0
- package/build/types/index.d.ts +13 -0
- package/build/umd/index.development.js +134 -0
- package/build/umd/index.development.js.map +1 -0
- package/build/umd/index.production.js +12 -0
- package/build/umd/index.production.js.map +1 -0
- package/package.json +12 -94
- package/src/index.tsx +105 -7
- package/dist/react-table.development.js +0 -3406
- package/dist/react-table.development.js.map +0 -1
- package/dist/react-table.production.min.js +0 -2
- package/dist/react-table.production.min.js.map +0 -1
- package/lib/index.js +0 -65
- package/src/aggregationTypes.ts +0 -115
- package/src/core.tsx +0 -1194
- package/src/createTable.tsx +0 -181
- package/src/features/Expanding.ts +0 -388
- package/src/features/Filters.ts +0 -707
- package/src/features/Grouping.ts +0 -451
- package/src/features/Headers.ts +0 -907
- package/src/features/Ordering.ts +0 -134
- package/src/features/Pinning.ts +0 -213
- package/src/features/Sorting.ts +0 -487
- package/src/features/Visibility.ts +0 -281
- package/src/features/notest/useAbsoluteLayout.test.js +0 -152
- package/src/features/notest/useBlockLayout.test.js +0 -158
- package/src/features/notest/useColumnOrder.test.js +0 -186
- package/src/features/notest/useExpanded.test.js +0 -125
- package/src/features/notest/useFilters.test.js +0 -393
- package/src/features/notest/useFiltersAndRowSelect.test.js +0 -256
- package/src/features/notest/useFlexLayout.test.js +0 -152
- package/src/features/notest/useGroupBy.test.js +0 -259
- package/src/features/notest/usePagination.test.js +0 -231
- package/src/features/notest/useResizeColumns.test.js +0 -229
- package/src/features/notest/useRowSelect.test.js +0 -250
- package/src/features/notest/useRowState.test.js +0 -178
- package/src/features/tests/Visibility.test.tsx +0 -225
- package/src/features/tests/__snapshots__/Visibility.test.tsx.snap +0 -390
- package/src/features/tests/withSorting.notest.tsx +0 -341
- package/src/features/withColumnResizing.ts +0 -281
- package/src/features/withPagination.ts +0 -208
- package/src/features/withRowSelection.ts +0 -467
- package/src/filterTypes.ts +0 -251
- package/src/sortTypes.ts +0 -159
- package/src/test-utils/makeTestData.ts +0 -41
- package/src/tests/__snapshots__/core.test.tsx.snap +0 -148
- package/src/tests/core.test.tsx +0 -241
- package/src/types.ts +0 -285
- package/src/utils/columnFilterRowsFn.ts +0 -162
- package/src/utils/expandRowsFn.ts +0 -53
- package/src/utils/globalFilterRowsFn.ts +0 -129
- package/src/utils/groupRowsFn.ts +0 -196
- package/src/utils/sortRowsFn.ts +0 -115
- package/src/utils.tsx +0 -243
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { render, fireEvent } from '@testing-library/react'
|
|
3
|
-
import { useTable } from '../../hooks/useTable'
|
|
4
|
-
import { useRowSelect } from '../useRowSelect'
|
|
5
|
-
import { useFilters } from '../useFilters'
|
|
6
|
-
|
|
7
|
-
const dataPiece = [
|
|
8
|
-
{
|
|
9
|
-
firstName: 'tanner',
|
|
10
|
-
lastName: 'linsley',
|
|
11
|
-
age: 29,
|
|
12
|
-
visits: 100,
|
|
13
|
-
status: 'In Relationship',
|
|
14
|
-
progress: 50,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
firstName: 'derek',
|
|
18
|
-
lastName: 'perkins',
|
|
19
|
-
age: 40,
|
|
20
|
-
visits: 40,
|
|
21
|
-
status: 'Single',
|
|
22
|
-
progress: 80,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
firstName: 'jaylen',
|
|
26
|
-
lastName: 'linsley',
|
|
27
|
-
age: 26,
|
|
28
|
-
visits: 99,
|
|
29
|
-
status: 'In Relationship',
|
|
30
|
-
progress: 70,
|
|
31
|
-
},
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
const data = [
|
|
35
|
-
...dataPiece,
|
|
36
|
-
...dataPiece,
|
|
37
|
-
...dataPiece,
|
|
38
|
-
...dataPiece,
|
|
39
|
-
...dataPiece,
|
|
40
|
-
...dataPiece,
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
function Table({ columns, data }) {
|
|
44
|
-
// Use the state and functions returned from useTable to build your UI
|
|
45
|
-
const {
|
|
46
|
-
getTableProps,
|
|
47
|
-
getTableBodyProps,
|
|
48
|
-
headerGroups,
|
|
49
|
-
rows,
|
|
50
|
-
prepareRow,
|
|
51
|
-
state,
|
|
52
|
-
} = useTable(
|
|
53
|
-
{
|
|
54
|
-
columns,
|
|
55
|
-
data,
|
|
56
|
-
defaultColumn,
|
|
57
|
-
},
|
|
58
|
-
useFilters,
|
|
59
|
-
useRowSelect
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
// Render the UI for your table
|
|
63
|
-
return (
|
|
64
|
-
<>
|
|
65
|
-
<table {...getTableProps()}>
|
|
66
|
-
<thead>
|
|
67
|
-
{headerGroups.map(headerGroup => (
|
|
68
|
-
<tr {...headerGroup.getHeaderGroupProps()}>
|
|
69
|
-
{headerGroup.headers.map(column => (
|
|
70
|
-
<th {...column.getHeaderProps()}>
|
|
71
|
-
{column.render('Header')}
|
|
72
|
-
{column.canFilter ? column.render('Filter') : null}
|
|
73
|
-
</th>
|
|
74
|
-
))}
|
|
75
|
-
</tr>
|
|
76
|
-
))}
|
|
77
|
-
</thead>
|
|
78
|
-
<tbody {...getTableBodyProps()}>
|
|
79
|
-
{rows.map(
|
|
80
|
-
(row, i) =>
|
|
81
|
-
prepareRow(row) || (
|
|
82
|
-
<tr {...row.getRowProps()}>
|
|
83
|
-
{row.cells.map(cell => {
|
|
84
|
-
return (
|
|
85
|
-
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
|
86
|
-
)
|
|
87
|
-
})}
|
|
88
|
-
</tr>
|
|
89
|
-
)
|
|
90
|
-
)}
|
|
91
|
-
</tbody>
|
|
92
|
-
</table>
|
|
93
|
-
<p>
|
|
94
|
-
Selected Rows:{' '}
|
|
95
|
-
<span data-testid="selected-count">
|
|
96
|
-
{Object.keys(state.selection).length}
|
|
97
|
-
</span>
|
|
98
|
-
</p>
|
|
99
|
-
<pre>
|
|
100
|
-
<code>{JSON.stringify({ selection: state.selection }, null, 2)}</code>
|
|
101
|
-
</pre>
|
|
102
|
-
</>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const defaultColumn = {
|
|
107
|
-
Cell: ({ value, column: { id } }) => `${id}: ${value}`,
|
|
108
|
-
Filter: ({ column: { filterValue, setFilter } }) => (
|
|
109
|
-
<input
|
|
110
|
-
value={filterValue || ''}
|
|
111
|
-
onChange={e => {
|
|
112
|
-
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
|
113
|
-
}}
|
|
114
|
-
placeholder="Search..."
|
|
115
|
-
/>
|
|
116
|
-
),
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const IndeterminateCheckbox = React.forwardRef(
|
|
120
|
-
({ indeterminate, ...rest }, ref) => {
|
|
121
|
-
const defaultRef = React.useRef()
|
|
122
|
-
const resolvedRef = ref || defaultRef
|
|
123
|
-
|
|
124
|
-
React.useEffect(() => {
|
|
125
|
-
resolvedRef.current.indeterminate = indeterminate
|
|
126
|
-
}, [resolvedRef, indeterminate])
|
|
127
|
-
|
|
128
|
-
return <input type="checkbox" ref={resolvedRef} {...rest} />
|
|
129
|
-
}
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
function App() {
|
|
133
|
-
const columns = React.useMemo(
|
|
134
|
-
() => [
|
|
135
|
-
{
|
|
136
|
-
id: 'selection',
|
|
137
|
-
// The header can use the table's getToggleAllRowsSelectedProps method
|
|
138
|
-
// to render a checkbox
|
|
139
|
-
Header: ({ getToggleAllRowsSelectedProps }) => (
|
|
140
|
-
<div>
|
|
141
|
-
<label>
|
|
142
|
-
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />{' '}
|
|
143
|
-
Select All
|
|
144
|
-
</label>
|
|
145
|
-
</div>
|
|
146
|
-
),
|
|
147
|
-
// The cell can use the individual row's getToggleRowSelectedProps method
|
|
148
|
-
// to the render a checkbox
|
|
149
|
-
Cell: ({ row }) => (
|
|
150
|
-
<div>
|
|
151
|
-
<label>
|
|
152
|
-
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />{' '}
|
|
153
|
-
Select Row
|
|
154
|
-
</label>
|
|
155
|
-
</div>
|
|
156
|
-
),
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
id: 'selectedStatus',
|
|
160
|
-
Cell: ({ row }) => (
|
|
161
|
-
<div>
|
|
162
|
-
Row {row.id} {row.isSelected ? 'Selected' : 'Not Selected'}
|
|
163
|
-
</div>
|
|
164
|
-
),
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
Header: 'Name',
|
|
168
|
-
columns: [
|
|
169
|
-
{
|
|
170
|
-
Header: 'First Name',
|
|
171
|
-
accessor: 'firstName',
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
Header: 'Last Name',
|
|
175
|
-
accessor: 'lastName',
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
Header: 'Info',
|
|
181
|
-
columns: [
|
|
182
|
-
{
|
|
183
|
-
Header: 'Age',
|
|
184
|
-
accessor: 'age',
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: 'visits',
|
|
188
|
-
Header: 'Visits',
|
|
189
|
-
accessor: 'visits',
|
|
190
|
-
filter: 'equals',
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
Header: 'Status',
|
|
194
|
-
accessor: 'status',
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
Header: 'Profile Progress',
|
|
198
|
-
accessor: 'progress',
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
[]
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
return <Table columns={columns} data={data} />
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
test('Select/Clear All while filtered only affects visible rows', () => {
|
|
210
|
-
const { getAllByPlaceholderText, getByLabelText, getByTestId } = render(
|
|
211
|
-
<App />
|
|
212
|
-
)
|
|
213
|
-
const selectedCount = getByTestId('selected-count')
|
|
214
|
-
const selectAllCheckbox = getByLabelText('Select All')
|
|
215
|
-
const fe = fireEvent
|
|
216
|
-
const filterInputs = getAllByPlaceholderText('Search...')
|
|
217
|
-
|
|
218
|
-
fireEvent.change(filterInputs[3], { target: { value: '40' } })
|
|
219
|
-
expect(selectedCount.textContent).toBe('0') // No selection has been made
|
|
220
|
-
|
|
221
|
-
expect(selectAllCheckbox.checked).toBe(false)
|
|
222
|
-
fireEvent.click(selectAllCheckbox)
|
|
223
|
-
expect(selectAllCheckbox.checked).toBe(true)
|
|
224
|
-
expect(selectedCount.textContent).toBe('6') // "Select All" has happened
|
|
225
|
-
|
|
226
|
-
// This filter hides all the rows (nothing matches it)
|
|
227
|
-
fireEvent.change(filterInputs[3], { target: { value: '10' } })
|
|
228
|
-
expect(selectedCount.textContent).toBe('6') // Filtering does not alter the selection
|
|
229
|
-
|
|
230
|
-
expect(selectAllCheckbox.checked).toBe(false) // None of the selected items are visible, this should be false
|
|
231
|
-
fireEvent.click(selectAllCheckbox) // The selection is is for no rows
|
|
232
|
-
expect(selectAllCheckbox.checked).toBe(false) // So clicking this checkbox does nothing
|
|
233
|
-
expect(selectedCount.textContent).toBe('6') // And does not affect the existing selection
|
|
234
|
-
|
|
235
|
-
fireEvent.change(filterInputs[3], { target: { value: '100' } })
|
|
236
|
-
expect(selectAllCheckbox.checked).toBe(false) // None of the selected items are visible, this should be false
|
|
237
|
-
fireEvent.click(selectAllCheckbox)
|
|
238
|
-
expect(selectAllCheckbox.checked).toBe(true) // Now all of the visible rows are ALSO selected
|
|
239
|
-
expect(selectedCount.textContent).toBe('12') // Clearing all should leave the original 6 selected
|
|
240
|
-
|
|
241
|
-
// Now deselect all the rows that match the filter
|
|
242
|
-
fireEvent.click(selectAllCheckbox)
|
|
243
|
-
expect(selectAllCheckbox.checked).toBe(false) // Now all of the visible rows are ALSO selected
|
|
244
|
-
expect(selectedCount.textContent).toBe('6') // Clearing all should leave the original 6 selected
|
|
245
|
-
|
|
246
|
-
fireEvent.change(filterInputs[3], { target: { value: '' } })
|
|
247
|
-
expect(selectedCount.textContent).toBe('6') // Clearing the Filter does not alter the selection
|
|
248
|
-
|
|
249
|
-
expect(selectAllCheckbox.checked).toBe(false) // Only a subset are selected so this button should show indeterminant
|
|
250
|
-
fireEvent.click(selectAllCheckbox)
|
|
251
|
-
expect(selectAllCheckbox.checked).toBe(true) // Now all of the visible rows are ALSO selected
|
|
252
|
-
expect(selectedCount.textContent).toBe(data.length.toString()) // Select All should select ALL of the rows
|
|
253
|
-
|
|
254
|
-
fireEvent.click(selectAllCheckbox)
|
|
255
|
-
expect(selectedCount.textContent).toBe('0') // Select All should now clear ALL of the rows
|
|
256
|
-
})
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useTable } from '../../hooks/useTable'
|
|
3
|
-
import { useFlexLayout } from '../useFlexLayout'
|
|
4
|
-
import { render } from '../../../test-utils/react-testing'
|
|
5
|
-
|
|
6
|
-
const data = [
|
|
7
|
-
{
|
|
8
|
-
firstName: 'tanner',
|
|
9
|
-
lastName: 'linsley',
|
|
10
|
-
age: 29,
|
|
11
|
-
visits: 100,
|
|
12
|
-
status: 'In Relationship',
|
|
13
|
-
progress: 50,
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
firstName: 'derek',
|
|
17
|
-
lastName: 'perkins',
|
|
18
|
-
age: 30,
|
|
19
|
-
visits: 40,
|
|
20
|
-
status: 'Single',
|
|
21
|
-
progress: 80,
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
firstName: 'joe',
|
|
25
|
-
lastName: 'bergevin',
|
|
26
|
-
age: 45,
|
|
27
|
-
visits: 20,
|
|
28
|
-
status: 'Complicated',
|
|
29
|
-
progress: 10,
|
|
30
|
-
},
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
const defaultColumn = {
|
|
34
|
-
Cell: ({ value, column: { id } }) => `${id}: ${value}`,
|
|
35
|
-
width: 200,
|
|
36
|
-
minWidth: 100,
|
|
37
|
-
maxWidth: 300,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function Table({ columns, data }) {
|
|
41
|
-
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
|
|
42
|
-
useTable(
|
|
43
|
-
{
|
|
44
|
-
columns,
|
|
45
|
-
data,
|
|
46
|
-
defaultColumn,
|
|
47
|
-
},
|
|
48
|
-
useFlexLayout
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<div {...getTableProps()} className="table">
|
|
53
|
-
<div>
|
|
54
|
-
{headerGroups.map(headerGroup => (
|
|
55
|
-
<div {...headerGroup.getHeaderGroupProps()} className="row">
|
|
56
|
-
{headerGroup.headers.map(column => (
|
|
57
|
-
<div {...column.getHeaderProps()} className="cell header">
|
|
58
|
-
{column.render('Header')}
|
|
59
|
-
</div>
|
|
60
|
-
))}
|
|
61
|
-
</div>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<div {...getTableBodyProps()}>
|
|
66
|
-
{rows.map(
|
|
67
|
-
(row, i) =>
|
|
68
|
-
prepareRow(row) || (
|
|
69
|
-
<div {...row.getRowProps()} className="row">
|
|
70
|
-
{row.cells.map(cell => {
|
|
71
|
-
return (
|
|
72
|
-
<div {...cell.getCellProps()} className="cell">
|
|
73
|
-
{cell.render('Cell')}
|
|
74
|
-
</div>
|
|
75
|
-
)
|
|
76
|
-
})}
|
|
77
|
-
</div>
|
|
78
|
-
)
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function App() {
|
|
86
|
-
const columns = React.useMemo(
|
|
87
|
-
() => [
|
|
88
|
-
{
|
|
89
|
-
Header: 'Name',
|
|
90
|
-
columns: [
|
|
91
|
-
{
|
|
92
|
-
Header: 'First Name',
|
|
93
|
-
accessor: 'firstName',
|
|
94
|
-
width: 250,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
Header: 'Last Name',
|
|
98
|
-
accessor: 'lastName',
|
|
99
|
-
width: 350,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
Header: 'Info',
|
|
105
|
-
columns: [
|
|
106
|
-
{
|
|
107
|
-
Header: 'Age',
|
|
108
|
-
accessor: 'age',
|
|
109
|
-
minWidth: 300,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
Header: 'Visits',
|
|
113
|
-
accessor: 'visits',
|
|
114
|
-
maxWidth: 150,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
Header: 'Status',
|
|
118
|
-
accessor: 'status',
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
Header: 'Profile Progress',
|
|
122
|
-
accessor: 'progress',
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
[]
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
return <Table columns={columns} data={data} />
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
test('renders a table', () => {
|
|
134
|
-
const rendered = render(<App />)
|
|
135
|
-
|
|
136
|
-
const [headerRow, , firstRow] = rendered.queryAllByRole('row')
|
|
137
|
-
|
|
138
|
-
expect(headerRow.getAttribute('style')).toEqual(
|
|
139
|
-
'display: flex; flex: 1 0 auto; min-width: 800px;'
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
expect(
|
|
143
|
-
Array.from(firstRow.children).map(d => d.getAttribute('style'))
|
|
144
|
-
).toEqual([
|
|
145
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 100px; width: 250px;',
|
|
146
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 100px; width: 300px;',
|
|
147
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 300px; width: 300px;',
|
|
148
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 100px; width: 150px;',
|
|
149
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 100px; width: 200px;',
|
|
150
|
-
'box-sizing: border-box; flex: 0 0 auto; min-width: 100px; width: 200px;',
|
|
151
|
-
])
|
|
152
|
-
})
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { render, fireEvent } from '../../../test-utils/react-testing'
|
|
3
|
-
import { useTable } from '../../hooks/useTable'
|
|
4
|
-
import { useGrouping } from '../useGrouping'
|
|
5
|
-
import { useExpanded } from '../useExpanded'
|
|
6
|
-
|
|
7
|
-
const data = [
|
|
8
|
-
{
|
|
9
|
-
firstName: 'tanner',
|
|
10
|
-
lastName: 'linsley',
|
|
11
|
-
age: 29,
|
|
12
|
-
visits: 100,
|
|
13
|
-
status: 'In Relationship',
|
|
14
|
-
progress: 50,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
firstName: 'derek',
|
|
18
|
-
lastName: 'perkins',
|
|
19
|
-
age: 40,
|
|
20
|
-
visits: 40,
|
|
21
|
-
status: 'Single',
|
|
22
|
-
progress: 80,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
firstName: 'joe',
|
|
26
|
-
lastName: 'bergevin',
|
|
27
|
-
age: 45,
|
|
28
|
-
visits: 20,
|
|
29
|
-
status: 'Complicated',
|
|
30
|
-
progress: 10,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
firstName: 'joe',
|
|
34
|
-
lastName: 'dirt',
|
|
35
|
-
age: 20,
|
|
36
|
-
visits: 5,
|
|
37
|
-
status: 'Complicated',
|
|
38
|
-
progress: 97,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
firstName: 'jaylen',
|
|
42
|
-
lastName: 'linsley',
|
|
43
|
-
age: 26,
|
|
44
|
-
visits: 99,
|
|
45
|
-
status: 'In Relationship',
|
|
46
|
-
progress: 70,
|
|
47
|
-
},
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
const defaultColumn = {
|
|
51
|
-
Cell: ({ value, column: { id } }) => `${id}: ${value}`,
|
|
52
|
-
Filter: ({ filterValue, setFilter }) => (
|
|
53
|
-
<input
|
|
54
|
-
value={filterValue || ''}
|
|
55
|
-
onChange={e => {
|
|
56
|
-
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
|
57
|
-
}}
|
|
58
|
-
placeholder="Search..."
|
|
59
|
-
/>
|
|
60
|
-
),
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function Table({ columns, data }) {
|
|
64
|
-
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
|
|
65
|
-
useTable(
|
|
66
|
-
{
|
|
67
|
-
columns,
|
|
68
|
-
data,
|
|
69
|
-
defaultColumn,
|
|
70
|
-
initialState: {
|
|
71
|
-
grouping: ["Column Doesn't Exist"],
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
useGrouping,
|
|
75
|
-
useExpanded
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<table {...getTableProps()}>
|
|
80
|
-
<thead>
|
|
81
|
-
{headerGroups.map(headerGroup => (
|
|
82
|
-
<tr {...headerGroup.getHeaderGroupProps()}>
|
|
83
|
-
{headerGroup.headers.map(column => (
|
|
84
|
-
<th {...column.getHeaderProps()}>
|
|
85
|
-
{column.canGroup ? (
|
|
86
|
-
// If the column can be grouped, let's add a toggle
|
|
87
|
-
<span {...column.getToggleGroupingProps()}>
|
|
88
|
-
{column.getIsGrouped() ? 'Ungroup' : 'Group'} {column.id}
|
|
89
|
-
</span>
|
|
90
|
-
) : null}
|
|
91
|
-
{column.render('Header')}
|
|
92
|
-
</th>
|
|
93
|
-
))}
|
|
94
|
-
</tr>
|
|
95
|
-
))}
|
|
96
|
-
</thead>
|
|
97
|
-
<tbody {...getTableBodyProps()}>
|
|
98
|
-
{rows.map(
|
|
99
|
-
(row, i) =>
|
|
100
|
-
prepareRow(row) || (
|
|
101
|
-
<tr {...row.getRowProps()}>
|
|
102
|
-
{row.cells.map(cell => {
|
|
103
|
-
return (
|
|
104
|
-
<td {...cell.getCellProps()}>
|
|
105
|
-
{cell.getIsGrouped() ? (
|
|
106
|
-
<>
|
|
107
|
-
<span
|
|
108
|
-
style={{
|
|
109
|
-
cursor: 'pointer',
|
|
110
|
-
}}
|
|
111
|
-
onClick={() => row.toggleRowExpanded()}
|
|
112
|
-
>
|
|
113
|
-
{row.getIsExpanded() ? '👇' : '👉'}
|
|
114
|
-
</span>
|
|
115
|
-
{cell.render('Cell')} ({row.subRows.length})
|
|
116
|
-
</>
|
|
117
|
-
) : cell.getIsAggregated() ? (
|
|
118
|
-
cell.render('Aggregated')
|
|
119
|
-
) : cell.getIsPlaceholder() ? null : (
|
|
120
|
-
cell.render('Cell')
|
|
121
|
-
)}
|
|
122
|
-
</td>
|
|
123
|
-
)
|
|
124
|
-
})}
|
|
125
|
-
</tr>
|
|
126
|
-
)
|
|
127
|
-
)}
|
|
128
|
-
</tbody>
|
|
129
|
-
</table>
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// This is a custom aggregator that
|
|
134
|
-
// takes in an array of leaf values and
|
|
135
|
-
// returns the rounded median
|
|
136
|
-
function roundedMedian(leafValues) {
|
|
137
|
-
let min = leafValues[0] || 0
|
|
138
|
-
let max = leafValues[0] || 0
|
|
139
|
-
|
|
140
|
-
leafValues.forEach(value => {
|
|
141
|
-
min = Math.min(min, value)
|
|
142
|
-
max = Math.max(max, value)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
return Math.round((min + max) / 2)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function App() {
|
|
149
|
-
const columns = React.useMemo(
|
|
150
|
-
() => [
|
|
151
|
-
{
|
|
152
|
-
Header: 'Name',
|
|
153
|
-
columns: [
|
|
154
|
-
{
|
|
155
|
-
Header: 'First Name',
|
|
156
|
-
accessor: 'firstName',
|
|
157
|
-
aggregate: 'count',
|
|
158
|
-
Aggregated: ({ value }) => `First Name Aggregated: ${value} Names`,
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
Header: 'Last Name',
|
|
162
|
-
accessor: 'lastName',
|
|
163
|
-
aggregate: 'uniqueCount',
|
|
164
|
-
Aggregated: ({ value }) =>
|
|
165
|
-
`Last Name Aggregated: ${value} Unique Names`,
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
Header: 'Info',
|
|
171
|
-
columns: [
|
|
172
|
-
{
|
|
173
|
-
Header: 'Age',
|
|
174
|
-
accessor: 'age',
|
|
175
|
-
aggregate: 'average',
|
|
176
|
-
Aggregated: ({ value }) => `Age Aggregated: ${value} (avg)`,
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
Header: 'Visits',
|
|
180
|
-
accessor: 'visits',
|
|
181
|
-
aggregate: 'sum',
|
|
182
|
-
Aggregated: ({ value }) => `Visits Aggregated: ${value} (total)`,
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
Header: 'Min Visits',
|
|
186
|
-
id: 'minVisits',
|
|
187
|
-
accessor: 'visits',
|
|
188
|
-
aggregate: 'min',
|
|
189
|
-
Aggregated: ({ value }) => `Visits Aggregated: ${value} (min)`,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
Header: 'Max Visits',
|
|
193
|
-
id: 'maxVisits',
|
|
194
|
-
accessor: 'visits',
|
|
195
|
-
aggregate: 'max',
|
|
196
|
-
Aggregated: ({ value }) => `Visits Aggregated: ${value} (max)`,
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
Header: 'Min/Max Visits',
|
|
200
|
-
id: 'minMaxVisits',
|
|
201
|
-
accessor: 'visits',
|
|
202
|
-
aggregate: 'minMax',
|
|
203
|
-
Aggregated: ({ value }) => `Visits Aggregated: ${value} (minMax)`,
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
Header: 'Status',
|
|
207
|
-
accessor: 'status',
|
|
208
|
-
aggregate: 'unique',
|
|
209
|
-
Aggregated: ({ value }) =>
|
|
210
|
-
`Visits Aggregated: ${value.join(', ')} (unique)`,
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
Header: 'Profile Progress (Median)',
|
|
214
|
-
accessor: 'progress',
|
|
215
|
-
id: 'progress',
|
|
216
|
-
aggregate: 'median',
|
|
217
|
-
Aggregated: ({ value }) => `Process Aggregated: ${value} (median)`,
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
Header: 'Profile Progress (Rounded Median)',
|
|
221
|
-
accessor: 'progress',
|
|
222
|
-
id: 'progressRounded',
|
|
223
|
-
aggregate: roundedMedian,
|
|
224
|
-
Aggregated: ({ value }) =>
|
|
225
|
-
`Process Aggregated: ${value} (rounded median)`,
|
|
226
|
-
},
|
|
227
|
-
],
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
[]
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
return <Table columns={columns} data={data} />
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
test('renders a groupable table', () => {
|
|
237
|
-
const rendered = render(<App />)
|
|
238
|
-
|
|
239
|
-
fireEvent.click(rendered.getByText('Group lastName'))
|
|
240
|
-
|
|
241
|
-
rendered.getByText('lastName: linsley (2)')
|
|
242
|
-
|
|
243
|
-
fireEvent.click(rendered.getByText('Group visits'))
|
|
244
|
-
|
|
245
|
-
fireEvent.click(rendered.getByText('Ungroup lastName'))
|
|
246
|
-
|
|
247
|
-
rendered.getByText('visits: 100 (1)')
|
|
248
|
-
|
|
249
|
-
fireEvent.click(rendered.getByText('Ungroup visits'))
|
|
250
|
-
|
|
251
|
-
fireEvent.click(rendered.getByText('Group firstName'))
|
|
252
|
-
|
|
253
|
-
rendered.getByText('firstName: tanner (1)')
|
|
254
|
-
|
|
255
|
-
rendered.debugDiff(false)
|
|
256
|
-
fireEvent.click(rendered.getByText('Group age'))
|
|
257
|
-
|
|
258
|
-
rendered.getByText('Last Name Aggregated: 2 Unique Names')
|
|
259
|
-
})
|