mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
Changed: Tests, aggregation, hooks, columnVisibility, docs
This commit is contained in:
parent
94a9b49187
commit
b989a8fa76
3
.babelrc
3
.babelrc
@ -11,7 +11,8 @@
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["@babel/preset-env", "@babel/react"]
|
||||
"presets": ["@babel/preset-env", "@babel/react"],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ react-table.css
|
||||
.DS_Store
|
||||
.history
|
||||
package-lock.json
|
||||
coverage
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
{
|
||||
"dist/index.js": {
|
||||
"bundled": 112721,
|
||||
"minified": 52301,
|
||||
"gzipped": 13754
|
||||
"bundled": 126199,
|
||||
"minified": 59470,
|
||||
"gzipped": 15298
|
||||
},
|
||||
"dist/index.es.js": {
|
||||
"bundled": 111784,
|
||||
"minified": 51465,
|
||||
"gzipped": 13587,
|
||||
"bundled": 125286,
|
||||
"minified": 58658,
|
||||
"gzipped": 15131,
|
||||
"treeshaked": {
|
||||
"rollup": {
|
||||
"code": 80,
|
||||
"import_statements": 21
|
||||
},
|
||||
"webpack": {
|
||||
"code": 8461
|
||||
"code": 8471
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,3 +1,24 @@
|
||||
## 7.0.0-rc.16
|
||||
|
||||
- Moved away from snapshot tests. No more testing implementation details.
|
||||
- Added `visibleColumns` and `visibleColumnsDeps` hooks to manipulate columns after all data is processed. Further visibility processing may result in these columns not being visible, such as `hiddenColumn` state
|
||||
- The `useRows` hook has been deprecated due to its dangerous nature 💀
|
||||
- Added the `instance.rowsById` object
|
||||
- Renamed `instance.flatColumns` to `instance.allColumns` which now accumulates ALL columns created for the table, visible or not.
|
||||
- Added the `instance.visibleColumns` object
|
||||
- Fix an issue where `useAsyncDebounce` would crash when passed arguments
|
||||
- Started development on the `usePivotColumns` plugin, which can be tested currently using the `_UNSTABLE_usePivoteColumns` export.
|
||||
- Renamed `cell.isRepeatedValue` to `cell.isPlaceholder`
|
||||
- Removed `useConsumeHookGetter` as it was inefficient most of the time and noisy
|
||||
- All hooks are now "consumed" right after main plugin functions are run. This means that any attempt to add a plugin after that will result in a runtime error (for good reason, since using hook points should not be a conditional or async operation)
|
||||
- Added `instance.getHooks` for getting the list of hooks that was captured after plugins are run
|
||||
- Normalized all "toggle" actions to use an optional `value` property to set the value instead of toggle. Previously properties like `selected`, `groupBy`, etc. were used, but not any more!
|
||||
- Undocument `instance.dispatch`. Both plugins and users should be interacting with the table via methods assigned to the instance and other structures on the table. This should both reduce the surface API that React Table needs to expose and also the amount of documentation that is needed to understand how to use the API.
|
||||
- `useRowState`'s `initialRowStateAccessor` and `initialCellStateAccessor` options now have a default of `row => ({})` and `cell => ({})` respectively.
|
||||
- Removed the concept of complex aggregations (eg. `column.aggregate = ['sum', 'count']`). Instead, a better aggregation function signature is now used to allow for leaf node aggregation when needed.
|
||||
- Added the `column.aggregateValue` option which allows resolving (or pre-aggregating) a cell's value before it is grouped and aggregated across rows. This is useful for cell values that are not primitive, eg. an array of values that you may want to unique and count before summing that count across your groupings
|
||||
- The function signature for aggregation functions has changed to be `(leafValues, aggregatedValues) => aggregatedValue` where `leafValues` is a flat array containing all leaf rows currently grouped at the aggregation level and `aggregatedValues` is an array containing the aggregated values from the immediate child sub rows. Each has purpose in the types of aggregations they power where optimizations are made for either accuracy or performance.
|
||||
|
||||
## 7.0.0-rc.15
|
||||
|
||||
- Added `useGlobalFilter` hook for performing table-wide filtering
|
||||
@ -9,7 +30,7 @@
|
||||
|
||||
- Changed the function signature for all propGetter hooks to accept a single object of named meta properties instead of a variable length of meta arguments. The user props object has also been added as a property to all prop getters. For example, `hooks.getRowProps.push((props, instance, row) => [...])` is now written `hooks.getRowProps.push((props, { instance, row, userProps }) => [...])`
|
||||
- Changed the function signature for all reduceHooks accept a single object of named meta properties instead of a variable length of meta arguments. For example, `hooks.flatColumns.push((flatColumns, instance) => flatColumns)` is now written `hooks.flatColumns.push((flatColumns, { instance }) => flatColumns)`
|
||||
- Changed the function signature for all loopHooks accept a single object of named meta properties instead of a variable length of meta arguments. For example, `hooks.prepareRow.push((row, instance) => void)` is now written `hooks.prepareRow.push((row { instance }) => void)`
|
||||
- Changed the function signature for all loopHooks accept a single object of named meta properties instead of a variable length of meta arguments. For example, `hooks.prepareRow.push((row, instance) => void)` is now written `hooks.prepareRow.push((row, { instance }) => void)`
|
||||
|
||||
## 7.0.0-rc.13
|
||||
|
||||
|
||||
@ -13,5 +13,5 @@ module.exports = {
|
||||
rootDir: path.resolve(__dirname, '../../'),
|
||||
roots: ['<rootDir>/src', __dirname],
|
||||
transformIgnorePatterns: ['node_modules'],
|
||||
snapshotSerializers: [require.resolve('snapshot-diff/serializer.js')],
|
||||
collectCoverageFrom: ['src/**/*.js', '!**/*.test.js'],
|
||||
}
|
||||
|
||||
@ -1,2 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import 'snapshot-diff/extend-expect'
|
||||
|
||||
@ -38,12 +38,18 @@ The following options are supported on any `Column` object passed to the `column
|
||||
- Receives the table instance and cell model as props
|
||||
- Must return valid JSX
|
||||
- This function (or component) formats this column's value when it is being grouped and aggregated, eg. If this column was showing the number of visits for a user to a website and it was currently being grouped to show an **average** of the values, the `Aggregated` function for this column could format that value to `1,000 Avg. Visits`
|
||||
- `aggregate: String | [String, String] | Function(values, rows, isAggregated: Bool) => any`
|
||||
- `aggregate: String | Function(leafValues, aggregatedValues) => any`
|
||||
- Optional
|
||||
- Used to aggregate values across rows, eg. `average`-ing the ages of many cells in a table"
|
||||
- If a single `String` is passed, it must be the key of either a user defined or predefined `aggregations` function.
|
||||
- If a tuple array of `[String, String]` is passed, both must be a key of either a user defined or predefined `aggregations` function.
|
||||
- The first is used to aggregate raw values, eg. `sum`-ing raw values together
|
||||
- The second is used to aggregate values that have already been aggregated, eg. `average`-ing the sums produced by the raw aggregation level
|
||||
- If a `Function` is passed, this function will receive the `values`, original `rows` of those values, and an `isAggregated` `Bool` of whether or not the values and rows have already been aggregated.
|
||||
- If a `Function` is passed, this function will receive both the leaf-row values and (if the rows have already been aggregated, the previously aggregated values) to be aggregated into a single value.
|
||||
- The function signature for all aggregation functions is `(leafValues, aggregatedValues) => aggregatedValue` where `leafValues` is a flat array containing all leaf rows currently grouped at the aggregation level and `aggregatedValues` is an array containing the aggregated values from the immediate child sub rows. Each has purpose in the types of aggregations they power where optimizations are made for either accuracy or performance.
|
||||
- For examples on how an aggregation functions work, see the source code for the built in aggregations in the [src/aggregations.js](../../src/aggregations.js) file.
|
||||
- `aggregateValue: String | Function(values, row, column) => any`
|
||||
- Optional
|
||||
- When attempting to group/aggregate non primitive cell values (eg. arrays of items) you will likely need to resolve a stable primitive value like a number or string to use in normal row aggregations. This property can be used to aggregate or simply access the value to be used in aggregations eg. `count`-ing the unique number of items in a cell's array value before `sum`-ing that count across the table.
|
||||
- If a single `String` is passed, it must be the key of either a user defined or predefined `aggregations` function.
|
||||
- If a `Function` is passed, this function will receive the cell's accessed value, the original `row` object and the `column` associated with the cell
|
||||
- `disableGroupBy: Boolean`
|
||||
- Defaults to `false`
|
||||
- If `true`, will disable grouping for this column.
|
||||
@ -92,6 +98,8 @@ The following properties are available on every `Row` object returned by the tab
|
||||
- This object contains the **aggregated** values for this row's sub rows
|
||||
- `subRows: Array<Row>`
|
||||
- If the row is a materialized group row, this property is the array of materialized subRows that were grouped inside of this row.
|
||||
- `leafRows: Array<Row>`
|
||||
- If the row is a materialized group row, this property is an array containing all leaf node rows aggregated into this row.
|
||||
- `depth: Int`
|
||||
- If the row is a materialized group row, this is the grouping depth at which this row was created.
|
||||
- `id: String`
|
||||
@ -108,7 +116,7 @@ The following additional properties are available on every `Cell` object returne
|
||||
|
||||
- `isGrouped: Bool`
|
||||
- If `true`, this cell is a grouped cell, meaning it contains a grouping value and should usually display and expander.
|
||||
- `isRepeatedValue: Bool`
|
||||
- `isPlaceholder: Bool`
|
||||
- If `true`, this cell is a repeated value cell, meaning it contains a value that is already being displayed elsewhere (usually by a parent row's cell).
|
||||
- Most of the time, this cell is not required to be displayed and can safely be hidden during rendering
|
||||
- `isAggregated: Bool`
|
||||
|
||||
@ -15,10 +15,16 @@ The following options are supported via the main options object passed to `useTa
|
||||
- If a row's ID is found in this array, it will have the state of the value corresponding to that key.
|
||||
- Individual row states can contain anything, but they also contain a `cellState` key, which provides cell-level state based on column ID's to every
|
||||
**prepared** cell in the table.
|
||||
- `initialRowStateAccessor: Function`
|
||||
- `initialRowStateAccessor: Function(originalRow) => Object<any>`
|
||||
- Optional
|
||||
- This function may optionally return the initial state for a row.
|
||||
- Defaults to: `row => ({})`
|
||||
- This function should return the initial state for a row.
|
||||
- If this function is defined, it will be passed a `Row` object, from which you can return a value to use as the initial state, eg. `row => row.original.initialState`
|
||||
- `initialCellStateAccessor: Function(originalRow) => Object<any>`
|
||||
- **Optional**
|
||||
- Defaults to: `cell => ({})`
|
||||
- This function should return the initial state for a cell.
|
||||
- If this function is defined, it will be passed a `Cell` object, from which you can return a value to use as the initial state, eg. `cell => cell.row.original.initialCellState[cell.column.id]`
|
||||
- `autoResetRowState: Boolean`
|
||||
- Defaults to `true`
|
||||
- When `true`, the `rowState` state will automatically reset if any of the following conditions are met:
|
||||
|
||||
@ -67,7 +67,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
|
||||
The following options are supported on any column object you can pass to `columns`.
|
||||
|
||||
- `accessor: String | Function`
|
||||
- `accessor: String | Function(originalRow, rowIndex) => any`
|
||||
- **Required**
|
||||
- This string/function is used to build the data model for your column.
|
||||
- The data returned by an accessor should be **primitive** and sortable.
|
||||
@ -120,18 +120,15 @@ The following properties are available on the table instance returned from `useT
|
||||
- `state: Object`
|
||||
- **Memoized** - This object reference will not change unless the internal table state is modified.
|
||||
- This is the final state object of the table, which is the product of the `initialState`, internal table reducer and (optionally) a custom `reducer` supplied by the user.
|
||||
- `dispatch: Function({ type: Actions[type], ...payload }) => void`
|
||||
- This function is used both internally by React Table, and optionally by you (the developer) to update the table state programmatically.
|
||||
- `type: Actions[type] | String`
|
||||
- The action type corresponding to what action being taken against the state.
|
||||
- `...payload`
|
||||
- Any other action data that is associated with the action
|
||||
- `columns: Array<Column>`
|
||||
- A **nested** array of final column objects, **similar in structure to the original columns configuration option**.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `flatColumns: Array<Column>`
|
||||
- `allColumns: Array<Column>`
|
||||
- A **flat** array of all final column objects.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `visibleColumns: Array<Column>`
|
||||
- A **flat** array of all visible column objects derived from `allColumns`.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `headerGroups: Array<HeaderGroup>`
|
||||
- An array of normalized header groups, each containing a flattened array of final column objects for that row.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
|
||||
@ -205,7 +205,7 @@ function Table({ columns, data }) {
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
flatColumns,
|
||||
visibleColumns,
|
||||
prepareRow,
|
||||
setColumnOrder,
|
||||
state,
|
||||
@ -230,7 +230,7 @@ function Table({ columns, data }) {
|
||||
)
|
||||
|
||||
const randomizeColumns = () => {
|
||||
setColumnOrder(shuffle(flatColumns.map(d => d.id)))
|
||||
setColumnOrder(shuffle(visibleColumns.map(d => d.id)))
|
||||
}
|
||||
|
||||
return (
|
||||
@ -267,30 +267,29 @@ function Table({ columns, data }) {
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
<AnimatePresence>
|
||||
{rows.slice(0, 10).map(
|
||||
(row, i) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<motion.tr
|
||||
{...row.getRowProps({
|
||||
layoutTransition: spring,
|
||||
exit: { opacity: 0, maxHeight: 0 },
|
||||
})}
|
||||
>
|
||||
{row.cells.map((cell, i) => {
|
||||
return (
|
||||
<motion.td
|
||||
{...cell.getCellProps({
|
||||
layoutTransition: spring,
|
||||
})}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</motion.td>
|
||||
)
|
||||
})}
|
||||
</motion.tr>
|
||||
)}
|
||||
)}
|
||||
{rows.slice(0, 10).map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<motion.tr
|
||||
{...row.getRowProps({
|
||||
layoutTransition: spring,
|
||||
exit: { opacity: 0, maxHeight: 0 },
|
||||
})}
|
||||
>
|
||||
{row.cells.map((cell, i) => {
|
||||
return (
|
||||
<motion.td
|
||||
{...cell.getCellProps({
|
||||
layoutTransition: spring,
|
||||
})}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</motion.td>
|
||||
)
|
||||
})}
|
||||
</motion.tr>
|
||||
)
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -59,17 +59,16 @@ function Table({ columns, data }) {
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
})}
|
||||
</tr>
|
||||
)}
|
||||
)}
|
||||
{rows.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
|
||||
@ -53,7 +53,7 @@ function Table({ columns, data }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
getToggleHideAllColumnsProps,
|
||||
state,
|
||||
} = useTable({
|
||||
@ -69,7 +69,7 @@ function Table({ columns, data }) {
|
||||
<IndeterminateCheckbox {...getToggleHideAllColumnsProps()} /> Toggle
|
||||
All
|
||||
</div>
|
||||
{flatColumns.map(column => (
|
||||
{allColumns.map(column => (
|
||||
<div key={column.id}>
|
||||
<label>
|
||||
<input type="checkbox" {...column.getToggleHiddenProps()} />{' '}
|
||||
|
||||
@ -50,7 +50,7 @@ function Table({ columns, data }) {
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
flatColumns,
|
||||
visibleColumns,
|
||||
prepareRow,
|
||||
setColumnOrder,
|
||||
state,
|
||||
@ -63,7 +63,7 @@ function Table({ columns, data }) {
|
||||
)
|
||||
|
||||
const randomizeColumns = () => {
|
||||
setColumnOrder(shuffle(flatColumns.map(d => d.id)))
|
||||
setColumnOrder(shuffle(visibleColumns.map(d => d.id)))
|
||||
}
|
||||
|
||||
return (
|
||||
@ -80,19 +80,16 @@ function Table({ columns, data }) {
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.slice(0, 10).map(
|
||||
(row, i) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell, i) => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)}
|
||||
)}
|
||||
{rows.slice(0, 10).map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell, i) => {
|
||||
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<pre>
|
||||
|
||||
@ -8087,10 +8087,10 @@ react-scripts@3.0.1:
|
||||
optionalDependencies:
|
||||
fsevents "2.0.6"
|
||||
|
||||
react-table@next:
|
||||
version "7.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-alpha.7.tgz#0cb6da6f32adb397e68505b7cdd4880d15d73017"
|
||||
integrity sha512-oXE9RRkE2CFk1OloNCSTPQ9qxOdujgkCoW5b/srbJsBog/ySkWuozBTQkxH1wGNmnSxGyTrTxJqXdXPQam7VAw==
|
||||
react-table@latest:
|
||||
version "7.0.0-rc.15"
|
||||
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-rc.15.tgz#bb855e4e2abbb4aaf0ed2334404a41f3ada8e13a"
|
||||
integrity sha512-ofMOlgrioHhhvHjvjsQkxvfQzU98cqwy6BjPGNwhLN1vhgXeWi0mUGreaCPvRenEbTiXsQbMl4k3Xmx3Mut8Rw==
|
||||
|
||||
react@^16.8.6:
|
||||
version "16.8.6"
|
||||
|
||||
@ -243,7 +243,7 @@ function Table({ columns, data }) {
|
||||
rows,
|
||||
prepareRow,
|
||||
state,
|
||||
flatColumns,
|
||||
visibleColumns,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
} = useTable(
|
||||
@ -278,7 +278,7 @@ function Table({ columns, data }) {
|
||||
))}
|
||||
<tr>
|
||||
<th
|
||||
colSpan={flatColumns.length}
|
||||
colSpan={visibleColumns.length}
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
}}
|
||||
|
||||
@ -65,7 +65,7 @@ function Table({ columns, data }) {
|
||||
// Our custom plugin to add the expander column
|
||||
hooks => {
|
||||
hooks.useControlledState.push(useControlledState)
|
||||
hooks.flatColumns.push((columns, { instance }) => {
|
||||
hooks.visibleColumns.push((columns, { instance }) => {
|
||||
if (!instance.state.groupBy.length) {
|
||||
return columns
|
||||
}
|
||||
@ -74,9 +74,9 @@ function Table({ columns, data }) {
|
||||
{
|
||||
id: 'expander', // Make sure it has an ID
|
||||
// Build our expander column
|
||||
Header: ({ flatColumns, state: { groupBy } }) => {
|
||||
Header: ({ allColumns, state: { groupBy } }) => {
|
||||
return groupBy.map(columnId => {
|
||||
const column = flatColumns.find(d => d.id === columnId)
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
|
||||
return (
|
||||
<span {...column.getHeaderProps()}>
|
||||
@ -166,7 +166,7 @@ function Table({ columns, data }) {
|
||||
? '#0aff0082'
|
||||
: cell.isAggregated
|
||||
? '#ffa50078'
|
||||
: cell.isRepeatedValue
|
||||
: cell.isPlaceholder
|
||||
? '#ff000042'
|
||||
: 'white',
|
||||
}}
|
||||
@ -175,7 +175,7 @@ function Table({ columns, data }) {
|
||||
? // If the cell is aggregated, use the Aggregated
|
||||
// renderer for cell
|
||||
cell.render('Aggregated')
|
||||
: cell.isRepeatedValue
|
||||
: cell.isPlaceholder
|
||||
? null // For cells with repeated values, render null
|
||||
: // Otherwise, just render the regular cell
|
||||
cell.render('Cell')}
|
||||
@ -225,20 +225,20 @@ function Legend() {
|
||||
padding: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Repeated Value
|
||||
Placeholder
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// This is a custom aggregator that
|
||||
// takes in an array of values and
|
||||
// takes in an array of leaf values and
|
||||
// returns the rounded median
|
||||
function roundedMedian(values) {
|
||||
let min = values[0] || ''
|
||||
let max = values[0] || ''
|
||||
function roundedMedian(leafValues) {
|
||||
let min = leafValues[0] || 0
|
||||
let max = leafValues[0] || 0
|
||||
|
||||
values.forEach(value => {
|
||||
leafValues.forEach(value => {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
})
|
||||
@ -259,7 +259,7 @@ function App() {
|
||||
// count the total rows being aggregated,
|
||||
// then sum any of those counts if they are
|
||||
// aggregated further
|
||||
aggregate: ['sum', 'count'],
|
||||
aggregate: 'count',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Names`,
|
||||
},
|
||||
{
|
||||
@ -269,7 +269,7 @@ function App() {
|
||||
// first count the UNIQUE values from the rows
|
||||
// being aggregated, then sum those counts if
|
||||
// they are aggregated further
|
||||
aggregate: ['sum', 'uniqueCount'],
|
||||
aggregate: 'uniqueCount',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Unique Names`,
|
||||
},
|
||||
],
|
||||
|
||||
@ -79,50 +79,49 @@ function Table({ columns, data }) {
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{firstPageRows.map(
|
||||
(row, i) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<td
|
||||
// For educational purposes, let's color the
|
||||
// cell depending on what type it is given
|
||||
// from the useGroupBy hook
|
||||
{...cell.getCellProps()}
|
||||
style={{
|
||||
background: cell.isGrouped
|
||||
? '#0aff0082'
|
||||
: cell.isAggregated
|
||||
? '#ffa50078'
|
||||
: cell.isRepeatedValue
|
||||
? '#ff000042'
|
||||
: 'white',
|
||||
}}
|
||||
>
|
||||
{cell.isGrouped ? (
|
||||
// If it's a grouped cell, add an expander and row count
|
||||
<>
|
||||
<span {...row.getExpandedToggleProps()}>
|
||||
{row.isExpanded ? '👇' : '👉'}
|
||||
</span>{' '}
|
||||
{cell.render('Cell')} ({row.subRows.length})
|
||||
</>
|
||||
) : cell.isAggregated ? (
|
||||
// If the cell is aggregated, use the Aggregated
|
||||
// renderer for cell
|
||||
cell.render('Aggregated')
|
||||
) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null
|
||||
// Otherwise, just render the regular cell
|
||||
cell.render('Cell')
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)}
|
||||
)}
|
||||
{firstPageRows.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<td
|
||||
// For educational purposes, let's color the
|
||||
// cell depending on what type it is given
|
||||
// from the useGroupBy hook
|
||||
{...cell.getCellProps()}
|
||||
style={{
|
||||
background: cell.isGrouped
|
||||
? '#0aff0082'
|
||||
: cell.isAggregated
|
||||
? '#ffa50078'
|
||||
: cell.isPlaceholder
|
||||
? '#ff000042'
|
||||
: 'white',
|
||||
}}
|
||||
>
|
||||
{cell.isGrouped ? (
|
||||
// If it's a grouped cell, add an expander and row count
|
||||
<>
|
||||
<span {...row.getExpandedToggleProps()}>
|
||||
{row.isExpanded ? '👇' : '👉'}
|
||||
</span>{' '}
|
||||
{cell.render('Cell')} ({row.subRows.length})
|
||||
</>
|
||||
) : cell.isAggregated ? (
|
||||
// If the cell is aggregated, use the Aggregated
|
||||
// renderer for cell
|
||||
cell.render('Aggregated')
|
||||
) : cell.isPlaceholder ? null : ( // For cells with repeated values, render null
|
||||
// Otherwise, just render the regular cell
|
||||
cell.render('Cell')
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
@ -170,13 +169,13 @@ function Legend() {
|
||||
}
|
||||
|
||||
// This is a custom aggregator that
|
||||
// takes in an array of values and
|
||||
// takes in an array of leaf values and
|
||||
// returns the rounded median
|
||||
function roundedMedian(values) {
|
||||
let min = values[0] || ''
|
||||
let max = values[0] || ''
|
||||
function roundedMedian(leafValues) {
|
||||
let min = leafValues[0] || 0
|
||||
let max = leafValues[0] || 0
|
||||
|
||||
values.forEach(value => {
|
||||
leafValues.forEach(value => {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
})
|
||||
@ -197,7 +196,7 @@ function App() {
|
||||
// count the total rows being aggregated,
|
||||
// then sum any of those counts if they are
|
||||
// aggregated further
|
||||
aggregate: ['sum', 'count'],
|
||||
aggregate: 'count',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Names`,
|
||||
},
|
||||
{
|
||||
@ -207,7 +206,7 @@ function App() {
|
||||
// first count the UNIQUE values from the rows
|
||||
// being aggregated, then sum those counts if
|
||||
// they are aggregated further
|
||||
aggregate: ['sum', 'uniqueCount'],
|
||||
aggregate: 'uniqueCount',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Unique Names`,
|
||||
},
|
||||
],
|
||||
@ -220,7 +219,8 @@ function App() {
|
||||
accessor: 'age',
|
||||
// Aggregate the average age of visitors
|
||||
aggregate: 'average',
|
||||
Aggregated: ({ cell: { value } }) => `${value} (avg)`,
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`${Math.round(value * 100) / 100} (avg)`,
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
|
||||
@ -308,7 +308,7 @@ function Table({ columns, data, updateMyData, skipPageReset }) {
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
hooks => {
|
||||
hooks.flatColumns.push(columns => [
|
||||
hooks.visibleColumns.push(columns => [
|
||||
{
|
||||
id: 'selection',
|
||||
// Make this column a groupByBoundary. This ensures that groupBy columns
|
||||
@ -388,7 +388,7 @@ function Table({ columns, data, updateMyData, skipPageReset }) {
|
||||
// If the cell is aggregated, use the Aggregated
|
||||
// renderer for cell
|
||||
cell.render('Aggregated')
|
||||
) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null
|
||||
) : cell.isPlaceholder ? null : ( // For cells with repeated values, render null
|
||||
// Otherwise, just render the regular cell
|
||||
cell.render('Cell', { editable: true })
|
||||
)}
|
||||
@ -486,13 +486,13 @@ function filterGreaterThan(rows, id, filterValue) {
|
||||
filterGreaterThan.autoRemove = val => typeof val !== 'number'
|
||||
|
||||
// This is a custom aggregator that
|
||||
// takes in an array of values and
|
||||
// takes in an array of leaf values and
|
||||
// returns the rounded median
|
||||
function roundedMedian(values) {
|
||||
let min = values[0] || ''
|
||||
let max = values[0] || ''
|
||||
function roundedMedian(leafValues) {
|
||||
let min = leafValues[0] || 0
|
||||
let max = leafValues[0] || 0
|
||||
|
||||
values.forEach(value => {
|
||||
leafValues.forEach(value => {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
})
|
||||
@ -513,7 +513,7 @@ function App() {
|
||||
// count the total rows being aggregated,
|
||||
// then sum any of those counts if they are
|
||||
// aggregated further
|
||||
aggregate: ['sum', 'count'],
|
||||
aggregate: 'count',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Names`,
|
||||
},
|
||||
{
|
||||
@ -525,7 +525,7 @@ function App() {
|
||||
// first count the UNIQUE values from the rows
|
||||
// being aggregated, then sum those counts if
|
||||
// they are aggregated further
|
||||
aggregate: ['sum', 'uniqueCount'],
|
||||
aggregate: 'uniqueCount',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Unique Names`,
|
||||
},
|
||||
],
|
||||
|
||||
@ -308,7 +308,7 @@ function Table({ columns, data, updateMyData, skipReset }) {
|
||||
useRowSelect,
|
||||
// Here we will use a plugin to add our selection column
|
||||
hooks => {
|
||||
hooks.flatColumns.push(columns => {
|
||||
hooks.visibleColumns.push(columns => {
|
||||
return [
|
||||
{
|
||||
id: 'selection',
|
||||
@ -390,7 +390,7 @@ function Table({ columns, data, updateMyData, skipReset }) {
|
||||
// If the cell is aggregated, use the Aggregated
|
||||
// renderer for cell
|
||||
cell.render('Aggregated')
|
||||
) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null
|
||||
) : cell.isPlaceholder ? null : ( // For cells with repeated values, render null
|
||||
// Otherwise, just render the regular cell
|
||||
cell.render('Cell', { editable: true })
|
||||
)}
|
||||
@ -488,13 +488,13 @@ function filterGreaterThan(rows, id, filterValue) {
|
||||
filterGreaterThan.autoRemove = val => typeof val !== 'number'
|
||||
|
||||
// This is a custom aggregator that
|
||||
// takes in an array of values and
|
||||
// takes in an array of leaf values and
|
||||
// returns the rounded median
|
||||
function roundedMedian(values) {
|
||||
let min = values[0] || ''
|
||||
let max = values[0] || ''
|
||||
function roundedMedian(leafValues) {
|
||||
let min = leafValues[0] || 0
|
||||
let max = leafValues[0] || 0
|
||||
|
||||
values.forEach(value => {
|
||||
leafValues.forEach(value => {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
})
|
||||
@ -532,7 +532,7 @@ function App() {
|
||||
// count the total rows being aggregated,
|
||||
// then sum any of those counts if they are
|
||||
// aggregated further
|
||||
aggregate: ['sum', 'count'],
|
||||
aggregate: 'count',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Names`,
|
||||
},
|
||||
{
|
||||
@ -544,7 +544,7 @@ function App() {
|
||||
// first count the UNIQUE values from the rows
|
||||
// being aggregated, then sum those counts if
|
||||
// they are aggregated further
|
||||
aggregate: ['sum', 'uniqueCount'],
|
||||
aggregate: 'uniqueCount',
|
||||
Aggregated: ({ cell: { value } }) => `${value} Unique Names`,
|
||||
},
|
||||
],
|
||||
|
||||
4
examples/pivoting/.babelrc
Normal file
4
examples/pivoting/.babelrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react-app"],
|
||||
"plugins": ["styled-components"]
|
||||
}
|
||||
1
examples/pivoting/.env
Normal file
1
examples/pivoting/.env
Normal file
@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
7
examples/pivoting/.eslintrc
Normal file
7
examples/pivoting/.eslintrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"rules": {
|
||||
// "eqeqeq": 0,
|
||||
// "jsx-a11y/anchor-is-valid": 0
|
||||
}
|
||||
}
|
||||
23
examples/pivoting/.gitignore
vendored
Normal file
23
examples/pivoting/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
29
examples/pivoting/.rescriptsrc.js
Normal file
29
examples/pivoting/.rescriptsrc.js
Normal file
@ -0,0 +1,29 @@
|
||||
const path = require('path')
|
||||
const resolveFrom = require('resolve-from')
|
||||
|
||||
const fixLinkedDependencies = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
react$: resolveFrom(path.resolve('node_modules'), 'react'),
|
||||
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const includeSrcDirectory = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
modules: [path.resolve('src'), ...config.resolve.modules],
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
['use-babel-config', '.babelrc'],
|
||||
['use-eslint-config', '.eslintrc'],
|
||||
fixLinkedDependencies,
|
||||
// includeSrcDirectory,
|
||||
]
|
||||
6
examples/pivoting/README.md
Normal file
6
examples/pivoting/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
|
||||
|
||||
You can:
|
||||
|
||||
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/pivoting)
|
||||
- `yarn` and `yarn start` to run and edit the example
|
||||
36
examples/pivoting/package.json
Normal file
36
examples/pivoting/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.8.18",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-table": "latest",
|
||||
"styled-components": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rescripts/cli": "^0.0.11",
|
||||
"@rescripts/rescript-use-babel-config": "^0.0.8",
|
||||
"@rescripts/rescript-use-eslint-config": "^0.0.9",
|
||||
"babel-eslint": "10.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
examples/pivoting/public/favicon.ico
Normal file
BIN
examples/pivoting/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
38
examples/pivoting/public/index.html
Normal file
38
examples/pivoting/public/index.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
examples/pivoting/public/manifest.json
Normal file
15
examples/pivoting/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
298
examples/pivoting/src/App.js
Normal file
298
examples/pivoting/src/App.js
Normal file
@ -0,0 +1,298 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
useTable,
|
||||
useGroupBy,
|
||||
useExpanded,
|
||||
_UNSTABLE_usePivoteColumns,
|
||||
} from 'react-table'
|
||||
import dayjs from 'dayjs'
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
|
||||
import makeData from './makeData'
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
const Styles = styled.div`
|
||||
padding: 1rem;
|
||||
white-space: nowrap;
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border: 1px solid black;
|
||||
|
||||
tr {
|
||||
:last-child {
|
||||
td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid black;
|
||||
border-right: 1px solid black;
|
||||
|
||||
:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
const defaultRef = React.useRef()
|
||||
const resolvedRef = ref || defaultRef
|
||||
|
||||
React.useEffect(() => {
|
||||
resolvedRef.current.indeterminate = indeterminate
|
||||
}, [resolvedRef, indeterminate])
|
||||
|
||||
return <input type="checkbox" ref={resolvedRef} {...rest} />
|
||||
}
|
||||
)
|
||||
|
||||
const renderHeaderToggles = headers => (
|
||||
<>
|
||||
{headers.map(column => (
|
||||
<div key={column.id}>
|
||||
<label>
|
||||
<input type="checkbox" {...column.getToggleHiddenProps()} />{' '}
|
||||
{column.id}
|
||||
</label>
|
||||
{column.headers && column.headers.length ? (
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: '2rem',
|
||||
}}
|
||||
>
|
||||
{renderHeaderToggles(column.headers)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
state,
|
||||
visibleColumns,
|
||||
allColumns,
|
||||
toggleGroupBy,
|
||||
togglePivot,
|
||||
getToggleHideAllColumnsProps,
|
||||
headers,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
},
|
||||
useGroupBy,
|
||||
_UNSTABLE_usePivoteColumns,
|
||||
useExpanded // useGroupBy and _UNSTABLE_usePivoteColumns would be pretty useless without useExpanded ;)
|
||||
)
|
||||
|
||||
// We don't want to render all of the rows for this example, so cap
|
||||
// it at 20 for this use case
|
||||
const firstPageRows = rows.slice(0, 25)
|
||||
|
||||
const options = allColumns.filter(
|
||||
d => !d.isGrouped && !d.isPivoted && !d.isPivotSource
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={visibleColumns.length}>
|
||||
Group By:{' '}
|
||||
{state.groupBy.map(columnId => {
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
return (
|
||||
<span key={column.id}>
|
||||
<button onClick={() => column.toggleGroupBy()}>
|
||||
🛑 {column.render('Header')}
|
||||
</button>{' '}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
<select
|
||||
onChange={e => toggleGroupBy(e.target.value, true)}
|
||||
value=""
|
||||
>
|
||||
<option disabled selected value="">
|
||||
Add column...{' '}
|
||||
</option>
|
||||
{options.map(column => (
|
||||
<option key={column.id} value={column.id}>
|
||||
{column.render('Header')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={visibleColumns.length}>
|
||||
Pivot Columns:
|
||||
{state.pivotColumns.map(columnId => {
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
return (
|
||||
<span key={column.id}>
|
||||
<button onClick={() => column.togglePivot()}>
|
||||
🛑 {column.render('Header')}
|
||||
</button>{' '}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
<select
|
||||
onChange={e => togglePivot(e.target.value, true)}
|
||||
value=""
|
||||
>
|
||||
<option disabled selected value="">
|
||||
Add column...{' '}
|
||||
</option>
|
||||
{options.map(column => (
|
||||
<option key={column.id} value={column.id}>
|
||||
{column.render('Header')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={visibleColumns.length}>
|
||||
<div>
|
||||
<IndeterminateCheckbox {...getToggleHideAllColumnsProps()} />{' '}
|
||||
Toggle All
|
||||
</div>
|
||||
{renderHeaderToggles(headers)}
|
||||
</td>
|
||||
</tr>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{firstPageRows.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>
|
||||
{cell.isGrouped ? (
|
||||
<>
|
||||
<span {...row.getExpandedToggleProps()}>
|
||||
{row.isExpanded ? '👇' : '👉'} {cell.render('Cell')}{' '}
|
||||
({row.subRows.length})
|
||||
</span>
|
||||
</>
|
||||
) : cell.isAggregated ? (
|
||||
cell.render('Aggregated')
|
||||
) : cell.isPlaceholder ? null : (
|
||||
cell.render('Cell')
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<div>Showing the first 25 results of {rows.length} rows</div>
|
||||
<pre>
|
||||
<code>{JSON.stringify(state, null, 2)}</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Order Date',
|
||||
id: 'date',
|
||||
accessor: d => new Date(d.date),
|
||||
sortType: 'basic',
|
||||
aggregate: 'count',
|
||||
Cell: ({ cell: { value } }) => (value ? dayjs(value).format('l') : ''),
|
||||
Aggregated: ({ cell: { value } }) => `${value} Orders`,
|
||||
},
|
||||
{
|
||||
Header: 'Employee',
|
||||
accessor: 'rep',
|
||||
aggregate: 'uniqueCount',
|
||||
},
|
||||
{
|
||||
Header: 'Region',
|
||||
accessor: 'region',
|
||||
aggregate: 'uniqueCount',
|
||||
},
|
||||
{
|
||||
Header: 'Item',
|
||||
accessor: 'item',
|
||||
aggregate: 'count',
|
||||
},
|
||||
{
|
||||
Header: 'Units',
|
||||
accessor: 'units',
|
||||
aggregate: 'sum',
|
||||
},
|
||||
{
|
||||
Header: 'Unit Cost',
|
||||
accessor: 'unitCost',
|
||||
aggregate: 'average',
|
||||
Cell: ({ cell: { value } }) =>
|
||||
typeof value !== 'undefined' ? (
|
||||
<div
|
||||
style={{ textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}
|
||||
>
|
||||
${(Math.floor(value * 100) / 100).toLocaleString()}
|
||||
</div>
|
||||
) : null,
|
||||
},
|
||||
{
|
||||
Header: 'Total',
|
||||
accessor: 'total',
|
||||
aggregate: 'sum',
|
||||
Cell: ({ cell: { value } }) =>
|
||||
typeof value !== 'undefined' ? (
|
||||
<div
|
||||
style={{ textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}
|
||||
>
|
||||
${(Math.floor(value * 100) / 100).toLocaleString()}
|
||||
</div>
|
||||
) : null,
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const data = React.useMemo(() => makeData(10000), [])
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
<Table columns={columns} data={data} />
|
||||
</Styles>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
9
examples/pivoting/src/App.test.js
Normal file
9
examples/pivoting/src/App.test.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(<App />, div)
|
||||
ReactDOM.unmountComponentAtNode(div)
|
||||
})
|
||||
13
examples/pivoting/src/index.css
Normal file
13
examples/pivoting/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
6
examples/pivoting/src/index.js
Normal file
6
examples/pivoting/src/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
95
examples/pivoting/src/makeData.js
Normal file
95
examples/pivoting/src/makeData.js
Normal file
@ -0,0 +1,95 @@
|
||||
const skewLow = n => (n < 0.9 ? n / 2 : n)
|
||||
|
||||
const sample = items => {
|
||||
const length = items.length
|
||||
const rand = Math.floor(skewLow(Math.random() * length))
|
||||
return items[rand]
|
||||
}
|
||||
|
||||
const reps = [
|
||||
'Jones',
|
||||
'Kivell',
|
||||
'Jardine',
|
||||
'Gill',
|
||||
'Sorvino',
|
||||
'Andrews',
|
||||
'Thompson',
|
||||
'Morgan',
|
||||
'Howard',
|
||||
'Parent',
|
||||
'Smith',
|
||||
]
|
||||
|
||||
const regions = ['East', 'Central', 'West', 'International']
|
||||
|
||||
const items = [
|
||||
['Pencil', 1.99, 100],
|
||||
['Binder', 19.99, 1, 50],
|
||||
['Pen', 7.99, 1, 100],
|
||||
['Desk', 299.99, 1, 10],
|
||||
['Notebook', 10.99, 1, 30],
|
||||
]
|
||||
|
||||
const dates = [
|
||||
'1/6/2018',
|
||||
'1/23/2018',
|
||||
'2/9/2018',
|
||||
'2/26/2018',
|
||||
'3/15/2018',
|
||||
'4/1/2018',
|
||||
'4/18/2018',
|
||||
'5/5/2018',
|
||||
'5/22/2018',
|
||||
'6/8/2018',
|
||||
'6/25/2018',
|
||||
'7/12/2018',
|
||||
'7/29/2018',
|
||||
'8/15/2018',
|
||||
'9/1/2018',
|
||||
'9/18/2018',
|
||||
'10/5/2018',
|
||||
'10/22/2018',
|
||||
'11/8/2018',
|
||||
'11/25/2018',
|
||||
'12/12/2018',
|
||||
'12/29/2018',
|
||||
'1/15/2019',
|
||||
'2/1/2019',
|
||||
'2/18/2019',
|
||||
'3/7/2019',
|
||||
'3/24/2019',
|
||||
'4/10/2019',
|
||||
'4/27/2019',
|
||||
'5/14/2019',
|
||||
'5/31/2019',
|
||||
'6/17/2019',
|
||||
'7/4/2019',
|
||||
'7/21/2019',
|
||||
'8/7/2019',
|
||||
'8/24/2019',
|
||||
'9/10/2019',
|
||||
'9/27/2019',
|
||||
'10/14/2019',
|
||||
'10/31/2019',
|
||||
'11/17/2019',
|
||||
'12/4/2019',
|
||||
'12/21/2019',
|
||||
]
|
||||
|
||||
export default function makeData() {
|
||||
return Array.from(new Array(10000)).map(() => {
|
||||
const [item, unitCost, stock] = sample(items)
|
||||
const units = Math.ceil(skewLow(Math.random()) * stock)
|
||||
const total = units * unitCost
|
||||
|
||||
return {
|
||||
date: sample(dates),
|
||||
rep: sample(reps),
|
||||
region: sample(regions),
|
||||
item,
|
||||
unitCost,
|
||||
units,
|
||||
total,
|
||||
}
|
||||
})
|
||||
}
|
||||
10150
examples/pivoting/yarn.lock
Normal file
10150
examples/pivoting/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -67,7 +67,7 @@ function Table({ columns, data }) {
|
||||
},
|
||||
useRowSelect,
|
||||
hooks => {
|
||||
hooks.flatColumns.push(columns => [
|
||||
hooks.visibleColumns.push(columns => [
|
||||
// Let's make a column for selection
|
||||
{
|
||||
id: 'selection',
|
||||
|
||||
@ -43,7 +43,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
flatColumns,
|
||||
visibleColumns,
|
||||
state: { expanded },
|
||||
} = useTable(
|
||||
{
|
||||
@ -88,7 +88,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
*/}
|
||||
{row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={flatColumns.length}>
|
||||
<td colSpan={visibleColumns.length}>
|
||||
{/*
|
||||
Inside it, call our renderRowSubComponent function. In reality,
|
||||
you could pass whatever you want as props to
|
||||
|
||||
11
package.json
11
package.json
@ -21,7 +21,8 @@
|
||||
"test": "is-ci \"test:ci\" \"test:dev\"",
|
||||
"test:dev": "jest --watch",
|
||||
"test:ci": "yarn test:jest",
|
||||
"test:jest": "jest",
|
||||
"test:jest": "jest --coverage",
|
||||
"test:coverage": "yarn test:jest; open coverage/lcov-report/index.html",
|
||||
"build": "cross-env NODE_ENV=production rollup -c",
|
||||
"start": "rollup -c -w",
|
||||
"prepare": "yarn build",
|
||||
@ -77,6 +78,7 @@
|
||||
"is-ci-cli": "^2.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-cli": "^24.9.0",
|
||||
"jest-diff": "^25.1.0",
|
||||
"jest-runner-eslint": "^0.7.5",
|
||||
"jest-watch-select-projects": "^1.0.0",
|
||||
"jest-watch-typeahead": "^0.4.2",
|
||||
@ -93,12 +95,15 @@
|
||||
"rollup-plugin-size": "^0.2.1",
|
||||
"rollup-plugin-size-snapshot": "^0.10.0",
|
||||
"rollup-plugin-terser": "^5.1.2",
|
||||
"snapshot-diff": "^0.6.1"
|
||||
"serve": "^11.2.0"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead"
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,76 @@
|
||||
export function sum(values, rows) {
|
||||
return values.reduce((sum, next) => sum + next, 0)
|
||||
export function sum(values, aggregatedValues) {
|
||||
// It's faster to just add the aggregations together instead of
|
||||
// process leaf nodes individually
|
||||
return aggregatedValues.reduce(
|
||||
(sum, next) => sum + (typeof next === 'number' ? next : 0),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
export function average(values, rows) {
|
||||
return Math.round((sum(values, rows) / values.length) * 100) / 100
|
||||
export function min(values) {
|
||||
let min = 0
|
||||
|
||||
values.forEach(value => {
|
||||
if (typeof value === 'number') {
|
||||
min = Math.min(min, value)
|
||||
}
|
||||
})
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
export function max(values) {
|
||||
let max = 0
|
||||
|
||||
values.forEach(value => {
|
||||
if (typeof value === 'number') {
|
||||
max = Math.max(max, value)
|
||||
}
|
||||
})
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
export function minMax(values) {
|
||||
let min = 0
|
||||
let max = 0
|
||||
|
||||
values.forEach(value => {
|
||||
if (typeof value === 'number') {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
}
|
||||
})
|
||||
|
||||
return `${min}..${max}`
|
||||
}
|
||||
|
||||
export function average(values) {
|
||||
return sum(null, values) / values.length
|
||||
}
|
||||
|
||||
export function median(values) {
|
||||
values = values.length ? values : [0]
|
||||
let min = Math.min(...values)
|
||||
let max = Math.max(...values)
|
||||
if (!values.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
let min = 0
|
||||
let max = 0
|
||||
|
||||
values.forEach(value => {
|
||||
if (typeof value === 'number') {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
}
|
||||
})
|
||||
|
||||
return (min + max) / 2
|
||||
}
|
||||
|
||||
export function unique(values) {
|
||||
return [...new Set(values).values()]
|
||||
}
|
||||
|
||||
export function uniqueCount(values) {
|
||||
return new Set(values).size
|
||||
}
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a basic table 1`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
tanner
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
29
|
||||
</td>
|
||||
<td>
|
||||
100
|
||||
</td>
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
derek
|
||||
</td>
|
||||
<td>
|
||||
perkins
|
||||
</td>
|
||||
<td>
|
||||
40
|
||||
</td>
|
||||
<td>
|
||||
40
|
||||
</td>
|
||||
<td>
|
||||
Single
|
||||
</td>
|
||||
<td>
|
||||
80
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
joe
|
||||
</td>
|
||||
<td>
|
||||
bergevin
|
||||
</td>
|
||||
<td>
|
||||
45
|
||||
</td>
|
||||
<td>
|
||||
20
|
||||
</td>
|
||||
<td>
|
||||
Complicated
|
||||
</td>
|
||||
<td>
|
||||
10
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -115,14 +115,12 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a basic table', () => {
|
||||
const { getByText, asFragment } = render(<App />)
|
||||
const rtl = render(<App />)
|
||||
|
||||
expect(getByText('tanner')).toBeInTheDocument()
|
||||
expect(getByText('linsley')).toBeInTheDocument()
|
||||
expect(getByText('29')).toBeInTheDocument()
|
||||
expect(getByText('100')).toBeInTheDocument()
|
||||
expect(getByText('In Relationship')).toBeInTheDocument()
|
||||
expect(getByText('50')).toBeInTheDocument()
|
||||
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(rtl.getByText('tanner')).toBeInTheDocument()
|
||||
expect(rtl.getByText('linsley')).toBeInTheDocument()
|
||||
expect(rtl.getByText('29')).toBeInTheDocument()
|
||||
expect(rtl.getByText('100')).toBeInTheDocument()
|
||||
expect(rtl.getByText('In Relationship')).toBeInTheDocument()
|
||||
expect(rtl.getByText('50')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -4,9 +4,8 @@ import {
|
||||
actions,
|
||||
functionalUpdate,
|
||||
useGetLatest,
|
||||
useConsumeHookGetter,
|
||||
makePropGetter,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
actions.resetHiddenColumns = 'resetHiddenColumns'
|
||||
actions.toggleHideColumn = 'toggleHideColumn'
|
||||
@ -104,7 +103,7 @@ function reducer(state, action, previousState, instance) {
|
||||
|
||||
return {
|
||||
...state,
|
||||
hiddenColumns: shouldAll ? instance.flatColumns.map(d => d.id) : [],
|
||||
hiddenColumns: shouldAll ? instance.allColumns.map(d => d.id) : [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,13 +149,14 @@ function useInstance(instance) {
|
||||
const {
|
||||
flatHeaders,
|
||||
dispatch,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
getHooks,
|
||||
state: { hiddenColumns },
|
||||
} = instance
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
const allColumnsHidden = flatColumns.length === hiddenColumns.length
|
||||
const allColumnsHidden = allColumns.length === hiddenColumns.length
|
||||
|
||||
const toggleHideColumn = React.useCallback(
|
||||
(columnId, value) =>
|
||||
@ -174,23 +174,11 @@ function useInstance(instance) {
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getToggleHideAllColumnsPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getToggleHideAllColumnsProps'
|
||||
)
|
||||
|
||||
const getToggleHideAllColumnsProps = makePropGetter(
|
||||
getToggleHideAllColumnsPropsHooks(),
|
||||
getHooks().getToggleHideAllColumnsProps,
|
||||
{ instance: getInstance() }
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getToggleHiddenPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getToggleHiddenProps'
|
||||
)
|
||||
|
||||
flatHeaders.forEach(column => {
|
||||
column.toggleHidden = value => {
|
||||
dispatch({
|
||||
@ -200,10 +188,13 @@ function useInstance(instance) {
|
||||
})
|
||||
}
|
||||
|
||||
column.getToggleHiddenProps = makePropGetter(getToggleHiddenPropsHooks(), {
|
||||
instance: getInstance(),
|
||||
column,
|
||||
})
|
||||
column.getToggleHiddenProps = makePropGetter(
|
||||
getHooks().getToggleHiddenProps,
|
||||
{
|
||||
instance: getInstance(),
|
||||
column,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
Object.assign(instance, {
|
||||
|
||||
@ -2,17 +2,23 @@ import React from 'react'
|
||||
|
||||
//
|
||||
import {
|
||||
actions,
|
||||
linkColumnStructure,
|
||||
flattenColumns,
|
||||
assignColumnAccessor,
|
||||
accessRowsForColumn,
|
||||
makeHeaderGroups,
|
||||
decorateColumn,
|
||||
dedupeBy,
|
||||
} from '../utils'
|
||||
|
||||
import {
|
||||
useGetLatest,
|
||||
reduceHooks,
|
||||
actions,
|
||||
loopHooks,
|
||||
makePropGetter,
|
||||
makeRenderer,
|
||||
decorateColumnTree,
|
||||
makeHeaderGroups,
|
||||
flattenBy,
|
||||
useGetLatest,
|
||||
useConsumeHookGetter,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
import makeDefaultPluginHooks from '../makeDefaultPluginHooks'
|
||||
|
||||
@ -73,15 +79,15 @@ export const useTable = (props, ...plugins) => {
|
||||
plugin(getInstance().hooks)
|
||||
})
|
||||
|
||||
const getUseOptionsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'useOptions'
|
||||
)
|
||||
// Consume all hooks and make a getter for them
|
||||
const getHooks = useGetLatest(getInstance().hooks)
|
||||
getInstance().getHooks = getHooks
|
||||
delete getInstance().hooks
|
||||
|
||||
// Allow useOptions hooks to modify the options coming into the table
|
||||
Object.assign(
|
||||
getInstance(),
|
||||
reduceHooks(getUseOptionsHooks(), applyDefaults(props))
|
||||
reduceHooks(getHooks().useOptions, applyDefaults(props))
|
||||
)
|
||||
|
||||
const {
|
||||
@ -95,12 +101,6 @@ export const useTable = (props, ...plugins) => {
|
||||
useControlledState,
|
||||
} = getInstance()
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getStateReducers = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'stateReducers'
|
||||
)
|
||||
|
||||
// Setup user reducer ref
|
||||
const getStateReducer = useGetLatest(stateReducer)
|
||||
|
||||
@ -115,7 +115,7 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Reduce the state from all plugin reducers
|
||||
return [
|
||||
...getStateReducers(),
|
||||
...getHooks().stateReducers,
|
||||
// Allow the user to add their own state reducer(s)
|
||||
...(Array.isArray(getStateReducer())
|
||||
? getStateReducer()
|
||||
@ -125,7 +125,7 @@ export const useTable = (props, ...plugins) => {
|
||||
state
|
||||
)
|
||||
},
|
||||
[getStateReducers, getStateReducer, getInstance]
|
||||
[getHooks, getStateReducer, getInstance]
|
||||
)
|
||||
|
||||
// Start the reducer
|
||||
@ -133,15 +133,9 @@ export const useTable = (props, ...plugins) => {
|
||||
reducer(initialState, { type: actions.init })
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getUseControlledStateHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'useControlledState'
|
||||
)
|
||||
|
||||
// Allow the user to control the final state with hooks
|
||||
const state = reduceHooks(
|
||||
[...getUseControlledStateHooks(), useControlledState],
|
||||
[...getHooks().useControlledState, useControlledState],
|
||||
reducerState,
|
||||
{ instance: getInstance() }
|
||||
)
|
||||
@ -151,170 +145,182 @@ export const useTable = (props, ...plugins) => {
|
||||
dispatch,
|
||||
})
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getColumnsHooks = useConsumeHookGetter(getInstance().hooks, 'columns')
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getColumnsDepsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'columnsDeps'
|
||||
)
|
||||
|
||||
// Decorate All the columns
|
||||
let columns = React.useMemo(
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
decorateColumnTree(
|
||||
reduceHooks(getColumnsHooks(), userColumns, {
|
||||
linkColumnStructure(
|
||||
reduceHooks(getHooks().columns, userColumns, {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
defaultColumn
|
||||
})
|
||||
),
|
||||
[
|
||||
defaultColumn,
|
||||
getColumnsHooks,
|
||||
getHooks,
|
||||
getInstance,
|
||||
userColumns,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(getColumnsDepsHooks(), [], { instance: getInstance() }),
|
||||
...reduceHooks(getHooks().columnsDeps, [], { instance: getInstance() }),
|
||||
]
|
||||
)
|
||||
|
||||
getInstance().columns = columns
|
||||
|
||||
// Get the flat list of all columns and allow hooks to decorate
|
||||
// those columns (and trigger this memoization via deps)
|
||||
let flatColumns = React.useMemo(() => flattenBy(columns, 'columns'), [
|
||||
columns,
|
||||
])
|
||||
let allColumns = React.useMemo(
|
||||
() =>
|
||||
reduceHooks(getHooks().allColumns, flattenColumns(columns), {
|
||||
instance: getInstance(),
|
||||
}).map(assignColumnAccessor),
|
||||
[
|
||||
columns,
|
||||
getHooks,
|
||||
getInstance,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(getHooks().allColumnsDeps, [], {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
]
|
||||
)
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
getInstance().flatColumns = flatColumns
|
||||
|
||||
// Access the row model
|
||||
const [rows, flatRows] = React.useMemo(() => {
|
||||
// Access the row model using initial columns
|
||||
const coreDataModel = React.useMemo(() => {
|
||||
let rows = []
|
||||
let flatRows = []
|
||||
const rowsById = {}
|
||||
|
||||
// Access the row's data
|
||||
const accessRow = (originalRow, i, depth = 0, parent) => {
|
||||
// Keep the original reference around
|
||||
const original = originalRow
|
||||
const allColumnsQueue = [...allColumns]
|
||||
|
||||
const id = getRowId(originalRow, i, parent)
|
||||
|
||||
const row = {
|
||||
id,
|
||||
original,
|
||||
index: i,
|
||||
depth,
|
||||
cells: [{}], // This is a dummy cell
|
||||
}
|
||||
|
||||
flatRows.push(row)
|
||||
|
||||
// Process any subRows
|
||||
let subRows = getSubRows(originalRow, i)
|
||||
|
||||
if (subRows) {
|
||||
row.subRows = subRows.map((d, i) => accessRow(d, i, depth + 1, row))
|
||||
}
|
||||
|
||||
// Override common array functions (and the dummy cell's getCellProps function)
|
||||
// to show an error if it is accessed without calling prepareRow
|
||||
const unpreparedAccessWarning = () => {
|
||||
throw new Error(
|
||||
'React-Table: You have not called prepareRow(row) one or more rows you are attempting to render.'
|
||||
)
|
||||
}
|
||||
row.cells.map = unpreparedAccessWarning
|
||||
row.cells.filter = unpreparedAccessWarning
|
||||
row.cells.forEach = unpreparedAccessWarning
|
||||
row.cells[0].getCellProps = unpreparedAccessWarning
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
flatColumns.forEach(({ id, accessor }) => {
|
||||
row.values[id] = accessor
|
||||
? accessor(originalRow, i, { subRows, depth, data })
|
||||
: undefined
|
||||
while (allColumnsQueue.length) {
|
||||
const column = allColumnsQueue.shift()
|
||||
accessRowsForColumn({
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
column,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
accessValueHooks: getHooks().accessValue,
|
||||
getInstance,
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// Use the resolved data
|
||||
const accessedData = data.map((d, i) => accessRow(d, i))
|
||||
return { rows, flatRows, rowsById }
|
||||
}, [allColumns, data, getRowId, getSubRows, getHooks, getInstance])
|
||||
|
||||
return [accessedData, flatRows]
|
||||
}, [data, flatColumns, getRowId, getSubRows])
|
||||
// Allow materialized columns to also access data
|
||||
const [rows, flatRows, rowsById, materializedColumns] = React.useMemo(() => {
|
||||
const { rows, flatRows, rowsById } = coreDataModel
|
||||
const materializedColumns = reduceHooks(
|
||||
getHooks().materializedColumns,
|
||||
[],
|
||||
{
|
||||
instance: getInstance(),
|
||||
}
|
||||
)
|
||||
|
||||
getInstance().rows = rows
|
||||
getInstance().flatRows = flatRows
|
||||
materializedColumns.forEach(d => assignColumnAccessor(d))
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const flatColumnsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'flatColumns'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const flatColumnsDepsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'flatColumnsDeps'
|
||||
const materializedColumnsQueue = [...materializedColumns]
|
||||
|
||||
while (materializedColumnsQueue.length) {
|
||||
const column = materializedColumnsQueue.shift()
|
||||
accessRowsForColumn({
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
column,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
accessValueHooks: getHooks().accessValue,
|
||||
getInstance,
|
||||
})
|
||||
}
|
||||
|
||||
return [rows, flatRows, rowsById, materializedColumns]
|
||||
}, [
|
||||
coreDataModel,
|
||||
getHooks,
|
||||
getInstance,
|
||||
data,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(getHooks().materializedColumnsDeps, [], {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
])
|
||||
|
||||
Object.assign(getInstance(), {
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
materializedColumns,
|
||||
})
|
||||
|
||||
loopHooks(getHooks().useInstanceAfterData, getInstance())
|
||||
|
||||
// Combine new materialized columns with all columns (dedupe prefers later columns)
|
||||
allColumns = React.useMemo(
|
||||
() => dedupeBy([...allColumns, ...materializedColumns], d => d.id),
|
||||
[allColumns, materializedColumns]
|
||||
)
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
// Get the flat list of all columns AFTER the rows
|
||||
// have been access, and allow hooks to decorate
|
||||
// those columns (and trigger this memoization via deps)
|
||||
flatColumns = React.useMemo(
|
||||
let visibleColumns = React.useMemo(
|
||||
() =>
|
||||
reduceHooks(flatColumnsHooks(), flatColumns, { instance: getInstance() }),
|
||||
reduceHooks(getHooks().visibleColumns, allColumns, {
|
||||
instance: getInstance(),
|
||||
}).map(d => decorateColumn(d, defaultColumn)),
|
||||
[
|
||||
flatColumns,
|
||||
flatColumnsHooks,
|
||||
getHooks,
|
||||
allColumns,
|
||||
getInstance,
|
||||
defaultColumn,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(flatColumnsDepsHooks(), [], { instance: getInstance() }),
|
||||
...reduceHooks(getHooks().visibleColumnsDeps, [], {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
getInstance().flatColumns = flatColumns
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getHeaderGroups = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'headerGroups'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getHeaderGroupsDeps = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'headerGroupsDeps'
|
||||
// Combine new visible columns with all columns (dedupe prefers later columns)
|
||||
allColumns = React.useMemo(
|
||||
() => dedupeBy([...allColumns, ...visibleColumns], d => d.id),
|
||||
[allColumns, visibleColumns]
|
||||
)
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
// Make the headerGroups
|
||||
const headerGroups = React.useMemo(
|
||||
() =>
|
||||
reduceHooks(
|
||||
getHeaderGroups(),
|
||||
makeHeaderGroups(flatColumns, defaultColumn),
|
||||
getHooks().headerGroups,
|
||||
makeHeaderGroups(visibleColumns, defaultColumn),
|
||||
getInstance()
|
||||
),
|
||||
[
|
||||
getHooks,
|
||||
visibleColumns,
|
||||
defaultColumn,
|
||||
flatColumns,
|
||||
getHeaderGroups,
|
||||
getInstance,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(getHeaderGroupsDeps(), [], { instance: getInstance() }),
|
||||
...reduceHooks(getHooks().headerGroupsDeps, [], {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
getInstance().headerGroups = headerGroups
|
||||
|
||||
// Get the first level of headers
|
||||
const headers = React.useMemo(
|
||||
() => (headerGroups.length ? headerGroups[0].headers : []),
|
||||
[headerGroups]
|
||||
)
|
||||
|
||||
getInstance().headers = headers
|
||||
|
||||
// Provide a flat header list for utilities
|
||||
@ -323,13 +329,21 @@ export const useTable = (props, ...plugins) => {
|
||||
[]
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getUseInstanceBeforeDimensions = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'useInstanceBeforeDimensions'
|
||||
)
|
||||
loopHooks(getHooks().useInstanceBeforeDimensions, getInstance())
|
||||
|
||||
loopHooks(getUseInstanceBeforeDimensions(), getInstance())
|
||||
// Filter columns down to visible ones
|
||||
const visibleColumnsDep = visibleColumns
|
||||
.filter(d => d.isVisible)
|
||||
.map(d => d.id)
|
||||
.sort()
|
||||
.join('_')
|
||||
|
||||
visibleColumns = React.useMemo(
|
||||
() => visibleColumns.filter(d => d.isVisible),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[visibleColumns, visibleColumnsDep]
|
||||
)
|
||||
getInstance().visibleColumns = visibleColumns
|
||||
|
||||
// Header Visibility is needed by this point
|
||||
const [
|
||||
@ -342,59 +356,29 @@ export const useTable = (props, ...plugins) => {
|
||||
getInstance().totalColumnsWidth = totalColumnsWidth
|
||||
getInstance().totalColumnsMaxWidth = totalColumnsMaxWidth
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getUseInstance = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'useInstance'
|
||||
)
|
||||
|
||||
loopHooks(getUseInstance(), getInstance())
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getHeaderPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getHeaderProps'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getFooterPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getFooterProps'
|
||||
)
|
||||
loopHooks(getHooks().useInstance, getInstance())
|
||||
|
||||
// Each materialized header needs to be assigned a render function and other
|
||||
// prop getter properties here.
|
||||
;[...getInstance().flatHeaders, ...getInstance().flatColumns].forEach(
|
||||
;[...getInstance().flatHeaders, ...getInstance().allColumns].forEach(
|
||||
column => {
|
||||
// Give columns/headers rendering power
|
||||
column.render = makeRenderer(getInstance(), column)
|
||||
|
||||
// Give columns/headers a default getHeaderProps
|
||||
column.getHeaderProps = makePropGetter(getHeaderPropsHooks(), {
|
||||
column.getHeaderProps = makePropGetter(getHooks().getHeaderProps, {
|
||||
instance: getInstance(),
|
||||
column,
|
||||
})
|
||||
|
||||
// Give columns/headers a default getFooterProps
|
||||
column.getFooterProps = makePropGetter(getFooterPropsHooks(), {
|
||||
column.getFooterProps = makePropGetter(getHooks().getFooterProps, {
|
||||
instance: getInstance(),
|
||||
column,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getHeaderGroupPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getHeaderGroupProps'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getFooterGroupPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getFooterGroupProps'
|
||||
)
|
||||
|
||||
getInstance().headerGroups = getInstance().headerGroups.filter(
|
||||
(headerGroup, i) => {
|
||||
// Filter out any headers and headerGroups that don't have visible columns
|
||||
@ -415,12 +399,12 @@ export const useTable = (props, ...plugins) => {
|
||||
// Give headerGroups getRowProps
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getHeaderGroupProps = makePropGetter(
|
||||
getHeaderGroupPropsHooks(),
|
||||
getHooks().getHeaderGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
headerGroup.getFooterGroupProps = makePropGetter(
|
||||
getFooterGroupPropsHooks(),
|
||||
getHooks().getFooterGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
@ -433,48 +417,18 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
getInstance().footerGroups = [...getInstance().headerGroups].reverse()
|
||||
|
||||
// Run the rows (this could be a dangerous hook with a ton of data)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getUseRowsHooks = useConsumeHookGetter(getInstance().hooks, 'useRows')
|
||||
|
||||
getInstance().rows = reduceHooks(getUseRowsHooks(), getInstance().rows, {
|
||||
instance: getInstance(),
|
||||
})
|
||||
|
||||
// The prepareRow function is absolutely necessary and MUST be called on
|
||||
// any rows the user wishes to be displayed.
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getPrepareRowHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'prepareRow'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getRowPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getRowProps'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getCellPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getCellProps'
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const cellsHooks = useConsumeHookGetter(getInstance().hooks, 'cells')
|
||||
|
||||
getInstance().prepareRow = React.useCallback(
|
||||
row => {
|
||||
row.getRowProps = makePropGetter(getRowPropsHooks(), {
|
||||
row.getRowProps = makePropGetter(getHooks().getRowProps, {
|
||||
instance: getInstance(),
|
||||
row,
|
||||
})
|
||||
|
||||
// Build the visible cells for each row
|
||||
row.allCells = flatColumns.map(column => {
|
||||
row.allCells = allColumns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
@ -482,7 +436,7 @@ export const useTable = (props, ...plugins) => {
|
||||
}
|
||||
|
||||
// Give each cell a getCellProps base
|
||||
cell.getCellProps = makePropGetter(getCellPropsHooks(), {
|
||||
cell.getCellProps = makePropGetter(getHooks().getCellProps, {
|
||||
instance: getInstance(),
|
||||
cell,
|
||||
})
|
||||
@ -496,50 +450,28 @@ export const useTable = (props, ...plugins) => {
|
||||
return cell
|
||||
})
|
||||
|
||||
row.cells = reduceHooks(cellsHooks(), row.allCells, {
|
||||
instance: getInstance(),
|
||||
})
|
||||
row.cells = visibleColumns.map(column =>
|
||||
row.allCells.find(cell => cell.column.id === column.id)
|
||||
)
|
||||
|
||||
// need to apply any row specific hooks (useExpanded requires this)
|
||||
loopHooks(getPrepareRowHooks(), row, { instance: getInstance() })
|
||||
loopHooks(getHooks().prepareRow, row, { instance: getInstance() })
|
||||
},
|
||||
[
|
||||
getRowPropsHooks,
|
||||
getInstance,
|
||||
flatColumns,
|
||||
cellsHooks,
|
||||
getPrepareRowHooks,
|
||||
getCellPropsHooks,
|
||||
]
|
||||
[getHooks, getInstance, allColumns, visibleColumns]
|
||||
)
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getTablePropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getTableProps'
|
||||
)
|
||||
|
||||
getInstance().getTableProps = makePropGetter(getTablePropsHooks(), {
|
||||
getInstance().getTableProps = makePropGetter(getHooks().getTableProps, {
|
||||
instance: getInstance(),
|
||||
})
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getTableBodyPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getTableBodyProps'
|
||||
getInstance().getTableBodyProps = makePropGetter(
|
||||
getHooks().getTableBodyProps,
|
||||
{
|
||||
instance: getInstance(),
|
||||
}
|
||||
)
|
||||
|
||||
getInstance().getTableBodyProps = makePropGetter(getTableBodyPropsHooks(), {
|
||||
instance: getInstance(),
|
||||
})
|
||||
|
||||
// Snapshot hook and disallow more from being added
|
||||
const getUseFinalInstanceHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'useFinalInstance'
|
||||
)
|
||||
|
||||
loopHooks(getUseFinalInstanceHooks(), getInstance())
|
||||
loopHooks(getHooks().useFinalInstance, getInstance())
|
||||
|
||||
return getInstance()
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ export { useGlobalFilter } from './plugin-hooks/useGlobalFilter'
|
||||
export { useGroupBy } from './plugin-hooks/useGroupBy'
|
||||
export { useSortBy } from './plugin-hooks/useSortBy'
|
||||
export { usePagination } from './plugin-hooks/usePagination'
|
||||
export { usePivotColumns as _UNSTABLE_usePivoteColumns } from './plugin-hooks/usePivotColumns'
|
||||
export { useRowSelect } from './plugin-hooks/useRowSelect'
|
||||
export { useRowState } from './plugin-hooks/useRowState'
|
||||
export { useColumnOrder } from './plugin-hooks/useColumnOrder'
|
||||
|
||||
@ -1,8 +1,17 @@
|
||||
const defaultCells = cell => cell.filter(d => d.column.isVisible)
|
||||
const defaultGetTableProps = props => ({
|
||||
role: 'table',
|
||||
...props,
|
||||
})
|
||||
|
||||
const defaultGetTableBodyProps = props => ({
|
||||
role: 'rowgroup',
|
||||
...props,
|
||||
})
|
||||
|
||||
const defaultGetHeaderProps = (props, { column }) => ({
|
||||
key: `header_${column.id}`,
|
||||
colSpan: column.totalVisibleHeaderCount,
|
||||
role: 'columnheader',
|
||||
...props,
|
||||
})
|
||||
|
||||
@ -14,6 +23,7 @@ const defaultGetFooterProps = (props, { column }) => ({
|
||||
|
||||
const defaultGetHeaderGroupProps = (props, { index }) => ({
|
||||
key: `headerGroup_${index}`,
|
||||
role: 'row',
|
||||
...props,
|
||||
})
|
||||
|
||||
@ -24,12 +34,14 @@ const defaultGetFooterGroupProps = (props, { index }) => ({
|
||||
|
||||
const defaultGetRowProps = (props, { row }) => ({
|
||||
key: `row_${row.id}`,
|
||||
role: 'row',
|
||||
...props,
|
||||
})
|
||||
|
||||
const defaultGetCellProps = (props, { cell }) => ({
|
||||
...props,
|
||||
key: `cell_${cell.row.id}_${cell.column.id}`,
|
||||
role: 'cell',
|
||||
...props,
|
||||
})
|
||||
|
||||
export default function makeDefaultPluginHooks() {
|
||||
@ -39,17 +51,21 @@ export default function makeDefaultPluginHooks() {
|
||||
useControlledState: [],
|
||||
columns: [],
|
||||
columnsDeps: [],
|
||||
flatColumns: [],
|
||||
flatColumnsDeps: [],
|
||||
allColumns: [],
|
||||
allColumnsDeps: [],
|
||||
accessValue: [],
|
||||
materializedColumns: [],
|
||||
materializedColumnsDeps: [],
|
||||
useInstanceAfterData: [],
|
||||
visibleColumns: [],
|
||||
visibleColumnsDeps: [],
|
||||
headerGroups: [],
|
||||
headerGroupsDeps: [],
|
||||
useInstanceBeforeDimensions: [],
|
||||
useInstance: [],
|
||||
useRows: [],
|
||||
cells: [defaultCells],
|
||||
prepareRow: [],
|
||||
getTableProps: [],
|
||||
getTableBodyProps: [],
|
||||
getTableProps: [defaultGetTableProps],
|
||||
getTableBodyProps: [defaultGetTableBodyProps],
|
||||
getHeaderGroupProps: [defaultGetHeaderGroupProps],
|
||||
getFooterGroupProps: [defaultGetFooterGroupProps],
|
||||
getHeaderProps: [defaultGetHeaderProps],
|
||||
|
||||
@ -1,205 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a table 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="table"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="row"
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="2"
|
||||
style="position: absolute; top: 0px; left: 0px; width: 550px;"
|
||||
>
|
||||
Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="4"
|
||||
style="position: absolute; top: 0px; left: 550px; width: 850px;"
|
||||
>
|
||||
Info
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 0px; width: 250px;"
|
||||
>
|
||||
First Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 250px; width: 300px;"
|
||||
>
|
||||
Last Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 550px; width: 300px;"
|
||||
>
|
||||
Age
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 850px; width: 150px;"
|
||||
>
|
||||
Visits
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 1000px; width: 200px;"
|
||||
>
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="position: absolute; top: 0px; left: 1200px; width: 200px;"
|
||||
>
|
||||
Profile Progress
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 0px; width: 250px;"
|
||||
>
|
||||
firstName: tanner
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 250px; width: 300px;"
|
||||
>
|
||||
lastName: linsley
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 550px; width: 300px;"
|
||||
>
|
||||
age: 29
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 850px; width: 150px;"
|
||||
>
|
||||
visits: 100
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1000px; width: 200px;"
|
||||
>
|
||||
status: In Relationship
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1200px; width: 200px;"
|
||||
>
|
||||
progress: 50
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 0px; width: 250px;"
|
||||
>
|
||||
firstName: derek
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 250px; width: 300px;"
|
||||
>
|
||||
lastName: perkins
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 550px; width: 300px;"
|
||||
>
|
||||
age: 30
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 850px; width: 150px;"
|
||||
>
|
||||
visits: 40
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1000px; width: 200px;"
|
||||
>
|
||||
status: Single
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1200px; width: 200px;"
|
||||
>
|
||||
progress: 80
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="position: relative; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 0px; width: 250px;"
|
||||
>
|
||||
firstName: joe
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 250px; width: 300px;"
|
||||
>
|
||||
lastName: bergevin
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 550px; width: 300px;"
|
||||
>
|
||||
age: 45
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 850px; width: 150px;"
|
||||
>
|
||||
visits: 20
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1000px; width: 200px;"
|
||||
>
|
||||
status: Complicated
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="position: absolute; top: 0px; left: 1200px; width: 200px;"
|
||||
>
|
||||
progress: 10
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -1,203 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a table 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="table"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="row"
|
||||
style="display: flex; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="2"
|
||||
style="display: inline-block; box-sizing: border-box; width: 550px;"
|
||||
>
|
||||
Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="4"
|
||||
style="display: inline-block; box-sizing: border-box; width: 850px;"
|
||||
>
|
||||
Info
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="display: flex; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 250px;"
|
||||
>
|
||||
First Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
Last Name
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
Age
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 150px;"
|
||||
>
|
||||
Visits
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
class="cell header"
|
||||
colspan="1"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
Profile Progress
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="row"
|
||||
style="display: flex; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 250px;"
|
||||
>
|
||||
firstName: tanner
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
lastName: linsley
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
age: 29
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 150px;"
|
||||
>
|
||||
visits: 100
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
status: In Relationship
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
progress: 50
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="display: flex; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 250px;"
|
||||
>
|
||||
firstName: derek
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
lastName: perkins
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
age: 30
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 150px;"
|
||||
>
|
||||
visits: 40
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
status: Single
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
progress: 80
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
style="display: flex; width: 1400px;"
|
||||
>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 250px;"
|
||||
>
|
||||
firstName: joe
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
lastName: bergevin
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 300px;"
|
||||
>
|
||||
age: 45
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 150px;"
|
||||
>
|
||||
visits: 20
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
status: Complicated
|
||||
</div>
|
||||
<div
|
||||
class="cell"
|
||||
style="display: inline-block; box-sizing: border-box; width: 200px;"
|
||||
>
|
||||
progress: 10
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -1,911 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders an expandable table 1`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<DocumentFragment>
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
- "expanded": {}
|
||||
+ "expanded": {
|
||||
+ "0": true
|
||||
+ }
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -64,10 +66,37 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
style="cursor: pointer; padding-left: 0rem;"
|
||||
+ >
|
||||
+ 👇
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ tanner
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 29
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 100
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 2rem;"
|
||||
>
|
||||
👉
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -85,10 +114,91 @@
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 2rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ derek
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ perkins
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Single
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 80
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 2rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ joe
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ bergevin
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 45
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 20
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Complicated
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 10
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 2rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ jaylen
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 26
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 99
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 70
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
`;
|
||||
|
||||
exports[`renders an expandable table 2`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<DocumentFragment>
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
"expanded": {
|
||||
- "0": true
|
||||
+ "0": true,
|
||||
+ "0.0": true
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<table>
|
||||
@@ -94,10 +95,37 @@
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
style="cursor: pointer; padding-left: 2rem;"
|
||||
>
|
||||
+ 👇
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ tanner
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 29
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 100
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 4rem;"
|
||||
+ >
|
||||
👉
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
tanner
|
||||
@@ -114,10 +142,91 @@
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 4rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ derek
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ perkins
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Single
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 80
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 4rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ joe
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ bergevin
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 45
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 20
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Complicated
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 10
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 4rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ jaylen
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 26
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 99
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 70
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
`;
|
||||
|
||||
exports[`renders an expandable table 3`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
"expanded": {
|
||||
"0": true,
|
||||
- "0.0": true
|
||||
+ "0.0": true,
|
||||
+ "0.0.0": true
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<table>
|
||||
@@ -122,10 +123,37 @@
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
style="cursor: pointer; padding-left: 4rem;"
|
||||
>
|
||||
+ 👇
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ tanner
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 29
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 100
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 6rem;"
|
||||
+ >
|
||||
👉
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
tanner
|
||||
@@ -142,10 +170,91 @@
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 6rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ derek
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ perkins
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 40
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Single
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 80
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 6rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ joe
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ bergevin
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 45
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 20
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ Complicated
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 10
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td>
|
||||
+ <span
|
||||
+ style="cursor: pointer; padding-left: 6rem;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ jaylen
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ linsley
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 26
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 99
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ In Relationship
|
||||
+ </td>
|
||||
+ <td>
|
||||
+ 70
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
`;
|
||||
|
||||
exports[`renders an expandable table 4`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
<code>
|
||||
{
|
||||
"expanded": {
|
||||
"0": true,
|
||||
"0.0": true,
|
||||
- "0.0.0": true
|
||||
+ "0.0.0": true,
|
||||
+ "0.0.0.0": true
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<table>
|
||||
@@ -150,11 +151,11 @@
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
style="cursor: pointer; padding-left: 6rem;"
|
||||
>
|
||||
- 👉
|
||||
+ 👇
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
tanner
|
||||
</td>
|
||||
@@ -170,10 +171,30 @@
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
+ </td>
|
||||
+ </tr>
|
||||
+ <tr>
|
||||
+ <td
|
||||
+ colspan="7"
|
||||
+ >
|
||||
+ <pre>
|
||||
+ <code>
|
||||
+ {
|
||||
+ "values": {
|
||||
+ "firstName": "tanner",
|
||||
+ "lastName": "linsley",
|
||||
+ "age": 29,
|
||||
+ "visits": 100,
|
||||
+ "status": "In Relationship",
|
||||
+ "progress": 50
|
||||
+ }
|
||||
+ }
|
||||
+ </code>
|
||||
+ </pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
`;
|
||||
|
||||
exports[`renders an expandable table 5`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<DocumentFragment>
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
- "expanded": {
|
||||
- "0": true,
|
||||
- "0.0": true,
|
||||
- "0.0.0": true,
|
||||
- "0.0.0.0": true
|
||||
- }
|
||||
+ "expanded": {}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -70,92 +65,11 @@
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
style="cursor: pointer; padding-left: 0rem;"
|
||||
>
|
||||
- 👇
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- tanner
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 29
|
||||
- </td>
|
||||
- <td>
|
||||
- 100
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 50
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 2rem;"
|
||||
- >
|
||||
- 👇
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- tanner
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 29
|
||||
- </td>
|
||||
- <td>
|
||||
- 100
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 50
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 4rem;"
|
||||
- >
|
||||
- 👇
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- tanner
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 29
|
||||
- </td>
|
||||
- <td>
|
||||
- 100
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 50
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 6rem;"
|
||||
- >
|
||||
- 👇
|
||||
+ 👉
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
tanner
|
||||
</td>
|
||||
@@ -171,273 +85,10 @@
|
||||
<td>
|
||||
In Relationship
|
||||
</td>
|
||||
<td>
|
||||
50
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td
|
||||
- colspan="7"
|
||||
- >
|
||||
- <pre>
|
||||
- <code>
|
||||
- {
|
||||
- "values": {
|
||||
- "firstName": "tanner",
|
||||
- "lastName": "linsley",
|
||||
- "age": 29,
|
||||
- "visits": 100,
|
||||
- "status": "In Relationship",
|
||||
- "progress": 50
|
||||
- }
|
||||
- }
|
||||
- </code>
|
||||
- </pre>
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 6rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- derek
|
||||
- </td>
|
||||
- <td>
|
||||
- perkins
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- Single
|
||||
- </td>
|
||||
- <td>
|
||||
- 80
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 6rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- joe
|
||||
- </td>
|
||||
- <td>
|
||||
- bergevin
|
||||
- </td>
|
||||
- <td>
|
||||
- 45
|
||||
- </td>
|
||||
- <td>
|
||||
- 20
|
||||
- </td>
|
||||
- <td>
|
||||
- Complicated
|
||||
- </td>
|
||||
- <td>
|
||||
- 10
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 6rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- jaylen
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 26
|
||||
- </td>
|
||||
- <td>
|
||||
- 99
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 70
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 4rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- derek
|
||||
- </td>
|
||||
- <td>
|
||||
- perkins
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- Single
|
||||
- </td>
|
||||
- <td>
|
||||
- 80
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 4rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- joe
|
||||
- </td>
|
||||
- <td>
|
||||
- bergevin
|
||||
- </td>
|
||||
- <td>
|
||||
- 45
|
||||
- </td>
|
||||
- <td>
|
||||
- 20
|
||||
- </td>
|
||||
- <td>
|
||||
- Complicated
|
||||
- </td>
|
||||
- <td>
|
||||
- 10
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 4rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- jaylen
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 26
|
||||
- </td>
|
||||
- <td>
|
||||
- 99
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 70
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 2rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- derek
|
||||
- </td>
|
||||
- <td>
|
||||
- perkins
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- 40
|
||||
- </td>
|
||||
- <td>
|
||||
- Single
|
||||
- </td>
|
||||
- <td>
|
||||
- 80
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 2rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- joe
|
||||
- </td>
|
||||
- <td>
|
||||
- bergevin
|
||||
- </td>
|
||||
- <td>
|
||||
- 45
|
||||
- </td>
|
||||
- <td>
|
||||
- 20
|
||||
- </td>
|
||||
- <td>
|
||||
- Complicated
|
||||
- </td>
|
||||
- <td>
|
||||
- 10
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- <span
|
||||
- style="cursor: pointer; padding-left: 2rem;"
|
||||
- >
|
||||
- 👉
|
||||
- </span>
|
||||
- </td>
|
||||
- <td>
|
||||
- jaylen
|
||||
- </td>
|
||||
- <td>
|
||||
- linsley
|
||||
- </td>
|
||||
- <td>
|
||||
- 26
|
||||
- </td>
|
||||
- <td>
|
||||
- 99
|
||||
- </td>
|
||||
- <td>
|
||||
- In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- 70
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span
|
||||
`;
|
||||
@ -1,766 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a filterable table 1`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
placeholder="Global search..."
|
||||
style="font-size: 1.1rem; border: 0px;"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 29
|
||||
</td>
|
||||
<td>
|
||||
visits: 100
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: derek
|
||||
</td>
|
||||
<td>
|
||||
lastName: perkins
|
||||
</td>
|
||||
<td>
|
||||
age: 40
|
||||
</td>
|
||||
<td>
|
||||
visits: 40
|
||||
</td>
|
||||
<td>
|
||||
status: Single
|
||||
</td>
|
||||
<td>
|
||||
progress: 80
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: joe
|
||||
</td>
|
||||
<td>
|
||||
lastName: bergevin
|
||||
</td>
|
||||
<td>
|
||||
age: 45
|
||||
</td>
|
||||
<td>
|
||||
visits: 20
|
||||
</td>
|
||||
<td>
|
||||
status: Complicated
|
||||
</td>
|
||||
<td>
|
||||
progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: jaylen
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 26
|
||||
</td>
|
||||
<td>
|
||||
visits: 99
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 70
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a filterable table 2`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value="l"
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
placeholder="Global search..."
|
||||
style="font-size: 1.1rem; border: 0px;"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 29
|
||||
</td>
|
||||
<td>
|
||||
visits: 100
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: jaylen
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 26
|
||||
</td>
|
||||
<td>
|
||||
visits: 99
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 70
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a filterable table 3`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value="er"
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
placeholder="Global search..."
|
||||
style="font-size: 1.1rem; border: 0px;"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: derek
|
||||
</td>
|
||||
<td>
|
||||
lastName: perkins
|
||||
</td>
|
||||
<td>
|
||||
age: 40
|
||||
</td>
|
||||
<td>
|
||||
visits: 40
|
||||
</td>
|
||||
<td>
|
||||
status: Single
|
||||
</td>
|
||||
<td>
|
||||
progress: 80
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: joe
|
||||
</td>
|
||||
<td>
|
||||
lastName: bergevin
|
||||
</td>
|
||||
<td>
|
||||
age: 45
|
||||
</td>
|
||||
<td>
|
||||
visits: 20
|
||||
</td>
|
||||
<td>
|
||||
status: Complicated
|
||||
</td>
|
||||
<td>
|
||||
progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a filterable table 4`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
placeholder="Global search..."
|
||||
style="font-size: 1.1rem; border: 0px;"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 29
|
||||
</td>
|
||||
<td>
|
||||
visits: 100
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: derek
|
||||
</td>
|
||||
<td>
|
||||
lastName: perkins
|
||||
</td>
|
||||
<td>
|
||||
age: 40
|
||||
</td>
|
||||
<td>
|
||||
visits: 40
|
||||
</td>
|
||||
<td>
|
||||
status: Single
|
||||
</td>
|
||||
<td>
|
||||
progress: 80
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: joe
|
||||
</td>
|
||||
<td>
|
||||
lastName: bergevin
|
||||
</td>
|
||||
<td>
|
||||
age: 45
|
||||
</td>
|
||||
<td>
|
||||
visits: 20
|
||||
</td>
|
||||
<td>
|
||||
status: Complicated
|
||||
</td>
|
||||
<td>
|
||||
progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: jaylen
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 26
|
||||
</td>
|
||||
<td>
|
||||
visits: 99
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 70
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a filterable table 5`] = `
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
colspan="4"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
First Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Last Name
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Age
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Visits
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Status
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
Profile Progress
|
||||
<input
|
||||
placeholder="Search..."
|
||||
value=""
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
placeholder="Global search..."
|
||||
style="font-size: 1.1rem; border: 0px;"
|
||||
value="li"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 29
|
||||
</td>
|
||||
<td>
|
||||
visits: 100
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: joe
|
||||
</td>
|
||||
<td>
|
||||
lastName: bergevin
|
||||
</td>
|
||||
<td>
|
||||
age: 45
|
||||
</td>
|
||||
<td>
|
||||
visits: 20
|
||||
</td>
|
||||
<td>
|
||||
status: Complicated
|
||||
</td>
|
||||
<td>
|
||||
progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: jaylen
|
||||
</td>
|
||||
<td>
|
||||
lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
age: 26
|
||||
</td>
|
||||
<td>
|
||||
visits: 99
|
||||
</td>
|
||||
<td>
|
||||
status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
progress: 70
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -1,294 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a groupable table 1`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -19,24 +19,24 @@
|
||||
>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
title="Toggle GroupBy"
|
||||
>
|
||||
- 👊
|
||||
+ 🛑
|
||||
</span>
|
||||
- First Name
|
||||
+ Last Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
title="Toggle GroupBy"
|
||||
>
|
||||
👊
|
||||
</span>
|
||||
- Last Name
|
||||
+ First Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
<span
|
||||
@@ -83,86 +83,81 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: tanner
|
||||
+ <span
|
||||
+ style="cursor: pointer;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ lastName: linsley (2)
|
||||
</td>
|
||||
<td>
|
||||
- lastName: linsley
|
||||
+ 2 Names
|
||||
</td>
|
||||
<td>
|
||||
- age: 29
|
||||
+ 27.5 (avg)
|
||||
</td>
|
||||
<td>
|
||||
- visits: 100
|
||||
- </td>
|
||||
- <td>
|
||||
- status: In Relationship
|
||||
- </td>
|
||||
- <td>
|
||||
- progress: 50
|
||||
- </td>
|
||||
- </tr>
|
||||
- <tr>
|
||||
- <td>
|
||||
- firstName: derek
|
||||
+ 199 (total)
|
||||
</td>
|
||||
<td>
|
||||
- lastName: perkins
|
||||
- </td>
|
||||
- <td>
|
||||
- age: 40
|
||||
- </td>
|
||||
- <td>
|
||||
- visits: 40
|
||||
- </td>
|
||||
- <td>
|
||||
- status: Single
|
||||
+ status: null
|
||||
</td>
|
||||
<td>
|
||||
- progress: 80
|
||||
+ 60 (med)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: joe
|
||||
+ <span
|
||||
+ style="cursor: pointer;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ lastName: perkins (1)
|
||||
</td>
|
||||
<td>
|
||||
- lastName: bergevin
|
||||
+ 1 Names
|
||||
</td>
|
||||
<td>
|
||||
- age: 45
|
||||
+ 40 (avg)
|
||||
</td>
|
||||
<td>
|
||||
- visits: 20
|
||||
+ 40 (total)
|
||||
</td>
|
||||
<td>
|
||||
- status: Complicated
|
||||
+ status: null
|
||||
</td>
|
||||
<td>
|
||||
- progress: 10
|
||||
+ 80 (med)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: jaylen
|
||||
+ <span
|
||||
+ style="cursor: pointer;"
|
||||
+ >
|
||||
+ 👉
|
||||
+ </span>
|
||||
+ lastName: bergevin (1)
|
||||
</td>
|
||||
<td>
|
||||
- lastName: linsley
|
||||
+ 1 Names
|
||||
</td>
|
||||
<td>
|
||||
- age: 26
|
||||
+ 45 (avg)
|
||||
</td>
|
||||
<td>
|
||||
- visits: 99
|
||||
+ 20 (total)
|
||||
</td>
|
||||
<td>
|
||||
- status: In Relationship
|
||||
+ status: null
|
||||
</td>
|
||||
<td>
|
||||
- progress: 70
|
||||
+ 10 (med)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a groupable table 2`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
<DocumentFragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
- colspan="2"
|
||||
+ colspan="1"
|
||||
+ >
|
||||
+ Name
|
||||
+ </th>
|
||||
+ <th
|
||||
+ colspan="1"
|
||||
+ >
|
||||
+ Info
|
||||
+ </th>
|
||||
+ <th
|
||||
+ colspan="1"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
- colspan="4"
|
||||
+ colspan="3"
|
||||
>
|
||||
Info
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -30,35 +40,35 @@
|
||||
>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
title="Toggle GroupBy"
|
||||
>
|
||||
- 👊
|
||||
+ 🛑
|
||||
</span>
|
||||
- First Name
|
||||
+ Visits
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
title="Toggle GroupBy"
|
||||
>
|
||||
👊
|
||||
</span>
|
||||
- Age
|
||||
+ First Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
title="Toggle GroupBy"
|
||||
>
|
||||
👊
|
||||
</span>
|
||||
- Visits
|
||||
+ Age
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
>
|
||||
<span
|
||||
@@ -90,18 +100,16 @@
|
||||
>
|
||||
👉
|
||||
</span>
|
||||
lastName: linsley (2)
|
||||
</td>
|
||||
+ <td />
|
||||
<td>
|
||||
2 Names
|
||||
</td>
|
||||
<td>
|
||||
27.5 (avg)
|
||||
- </td>
|
||||
- <td>
|
||||
- 199 (total)
|
||||
</td>
|
||||
<td>
|
||||
status: null
|
||||
</td>
|
||||
<td>
|
||||
@@ -115,20 +123,18 @@
|
||||
>
|
||||
👉
|
||||
</span>
|
||||
lastName: perkins (1)
|
||||
</td>
|
||||
+ <td />
|
||||
<td>
|
||||
1 Names
|
||||
</td>
|
||||
<td>
|
||||
40 (avg)
|
||||
</td>
|
||||
<td>
|
||||
- 40 (total)
|
||||
- </td>
|
||||
- <td>
|
||||
status: null
|
||||
</td>
|
||||
<td>
|
||||
80 (med)
|
||||
</td>
|
||||
@@ -140,18 +146,16 @@
|
||||
>
|
||||
👉
|
||||
</span>
|
||||
lastName: bergevin (1)
|
||||
</td>
|
||||
+ <td />
|
||||
<td>
|
||||
1 Names
|
||||
</td>
|
||||
<td>
|
||||
45 (avg)
|
||||
- </td>
|
||||
- <td>
|
||||
- 20 (total)
|
||||
</td>
|
||||
<td>
|
||||
status: null
|
||||
</td>
|
||||
<td>
|
||||
`;
|
||||
@ -1,498 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a paginated table 1`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 21
|
||||
+ tanner 31
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -67,11 +67,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 22
|
||||
+ tanner 32
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -87,11 +87,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 23
|
||||
+ tanner 33
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -107,11 +107,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 24
|
||||
+ tanner 34
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -127,11 +127,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 25
|
||||
+ tanner 35
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -147,11 +147,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 26
|
||||
+ tanner 36
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -167,11 +167,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 27
|
||||
+ tanner 37
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -187,11 +187,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 28
|
||||
+ tanner 38
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -207,11 +207,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 29
|
||||
+ tanner 39
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -227,11 +227,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 30
|
||||
+ tanner 40
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -269,20 +269,20 @@
|
||||
</button>
|
||||
|
||||
<span>
|
||||
Page
|
||||
<strong>
|
||||
- 3 of 10
|
||||
+ 4 of 10
|
||||
</strong>
|
||||
|
||||
</span>
|
||||
<span>
|
||||
| Go to page:
|
||||
<input
|
||||
style="width: 100px;"
|
||||
type="number"
|
||||
- value="3"
|
||||
+ value="4"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<select>
|
||||
<option
|
||||
`;
|
||||
|
||||
exports[`renders a paginated table 2`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 31
|
||||
+ tanner 41
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -67,11 +67,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 32
|
||||
+ tanner 42
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -87,11 +87,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 33
|
||||
+ tanner 43
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -107,11 +107,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 34
|
||||
+ tanner 44
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -127,11 +127,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 35
|
||||
+ tanner 45
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -147,11 +147,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 36
|
||||
+ tanner 46
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -167,11 +167,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 37
|
||||
+ tanner 47
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -187,11 +187,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 38
|
||||
+ tanner 48
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -207,11 +207,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 39
|
||||
+ tanner 49
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -227,11 +227,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 40
|
||||
+ tanner 50
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -269,20 +269,20 @@
|
||||
</button>
|
||||
|
||||
<span>
|
||||
Page
|
||||
<strong>
|
||||
- 4 of 10
|
||||
+ 5 of 10
|
||||
</strong>
|
||||
|
||||
</span>
|
||||
<span>
|
||||
| Go to page:
|
||||
<input
|
||||
style="width: 100px;"
|
||||
type="number"
|
||||
- value="4"
|
||||
+ value="5"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<select>
|
||||
<option
|
||||
`;
|
||||
|
||||
exports[`renders a paginated table 3`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 41
|
||||
+ tanner 91
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -67,11 +67,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 42
|
||||
+ tanner 92
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -87,11 +87,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 43
|
||||
+ tanner 93
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -107,11 +107,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 44
|
||||
+ tanner 94
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -127,11 +127,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 45
|
||||
+ tanner 95
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -147,11 +147,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 46
|
||||
+ tanner 96
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -167,11 +167,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 47
|
||||
+ tanner 97
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -187,11 +187,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 48
|
||||
+ tanner 98
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -207,11 +207,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 49
|
||||
+ tanner 99
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -227,11 +227,11 @@
|
||||
50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- tanner 50
|
||||
+ tanner 100
|
||||
</td>
|
||||
<td>
|
||||
linsley
|
||||
</td>
|
||||
<td>
|
||||
@@ -258,31 +258,35 @@
|
||||
|
||||
<button>
|
||||
<
|
||||
</button>
|
||||
|
||||
- <button>
|
||||
+ <button
|
||||
+ disabled=""
|
||||
+ >
|
||||
>
|
||||
</button>
|
||||
|
||||
- <button>
|
||||
+ <button
|
||||
+ disabled=""
|
||||
+ >
|
||||
>>
|
||||
</button>
|
||||
|
||||
<span>
|
||||
Page
|
||||
<strong>
|
||||
- 5 of 10
|
||||
+ 10 of 10
|
||||
</strong>
|
||||
|
||||
</span>
|
||||
<span>
|
||||
| Go to page:
|
||||
<input
|
||||
style="width: 100px;"
|
||||
type="number"
|
||||
- value="5"
|
||||
+ value="10"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<select>
|
||||
<option
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,194 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders a sortable table 1`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
colspan="1"
|
||||
style="cursor: pointer;"
|
||||
title="Toggle SortBy"
|
||||
>
|
||||
First Name
|
||||
- <span />
|
||||
+ <span>
|
||||
+ 🔼
|
||||
+ </span>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
style="cursor: pointer;"
|
||||
title="Toggle SortBy"
|
||||
@@ -67,66 +69,66 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: tanner
|
||||
+ firstName: derek
|
||||
</td>
|
||||
<td>
|
||||
- lastName: linsley
|
||||
+ lastName: perkins
|
||||
</td>
|
||||
<td>
|
||||
- age: 29
|
||||
+ age: 40
|
||||
</td>
|
||||
<td>
|
||||
- visits: 100
|
||||
+ visits: 40
|
||||
</td>
|
||||
<td>
|
||||
- status: In Relationship
|
||||
+ status: Single
|
||||
</td>
|
||||
<td>
|
||||
- progress: 50
|
||||
+ progress: 80
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: derek
|
||||
+ firstName: joe
|
||||
</td>
|
||||
<td>
|
||||
- lastName: perkins
|
||||
+ lastName: bergevin
|
||||
</td>
|
||||
<td>
|
||||
- age: 40
|
||||
+ age: 45
|
||||
</td>
|
||||
<td>
|
||||
- visits: 40
|
||||
+ visits: 20
|
||||
</td>
|
||||
<td>
|
||||
- status: Single
|
||||
+ status: Complicated
|
||||
</td>
|
||||
<td>
|
||||
- progress: 80
|
||||
+ progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: joe
|
||||
+ firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
- lastName: bergevin
|
||||
+ lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
- age: 45
|
||||
+ age: 29
|
||||
</td>
|
||||
<td>
|
||||
- visits: 20
|
||||
+ visits: 100
|
||||
</td>
|
||||
<td>
|
||||
- status: Complicated
|
||||
+ status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
- progress: 10
|
||||
+ progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders a sortable table 2`] = `
|
||||
Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
style="cursor: pointer;"
|
||||
title="Toggle SortBy"
|
||||
>
|
||||
First Name
|
||||
<span>
|
||||
- 🔼
|
||||
+ 🔽
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
style="cursor: pointer;"
|
||||
@@ -69,26 +69,26 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: derek
|
||||
+ firstName: tanner
|
||||
</td>
|
||||
<td>
|
||||
- lastName: perkins
|
||||
+ lastName: linsley
|
||||
</td>
|
||||
<td>
|
||||
- age: 40
|
||||
+ age: 29
|
||||
</td>
|
||||
<td>
|
||||
- visits: 40
|
||||
+ visits: 100
|
||||
</td>
|
||||
<td>
|
||||
- status: Single
|
||||
+ status: In Relationship
|
||||
</td>
|
||||
<td>
|
||||
- progress: 80
|
||||
+ progress: 50
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
firstName: joe
|
||||
@@ -109,26 +109,26 @@
|
||||
progress: 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- firstName: tanner
|
||||
+ firstName: derek
|
||||
</td>
|
||||
<td>
|
||||
- lastName: linsley
|
||||
+ lastName: perkins
|
||||
</td>
|
||||
<td>
|
||||
- age: 29
|
||||
+ age: 40
|
||||
</td>
|
||||
<td>
|
||||
- visits: 100
|
||||
+ visits: 40
|
||||
</td>
|
||||
<td>
|
||||
- status: In Relationship
|
||||
+ status: Single
|
||||
</td>
|
||||
<td>
|
||||
- progress: 50
|
||||
+ progress: 80
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -136,7 +136,22 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a table', () => {
|
||||
const { asFragment } = render(<App />)
|
||||
const rtl = render(<App />)
|
||||
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(
|
||||
rtl.getAllByRole('columnheader').every(d => d.style.position === 'absolute')
|
||||
).toBe(true)
|
||||
|
||||
expect(
|
||||
rtl.getAllByRole('columnheader').map(d => [d.style.left, d.style.width])
|
||||
).toStrictEqual([
|
||||
['0px', '550px'],
|
||||
['550px', '850px'],
|
||||
['0px', '250px'],
|
||||
['250px', '300px'],
|
||||
['550px', '300px'],
|
||||
['850px', '150px'],
|
||||
['1000px', '200px'],
|
||||
['1200px', '200px'],
|
||||
])
|
||||
})
|
||||
|
||||
@ -136,7 +136,28 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a table', () => {
|
||||
const { asFragment } = render(<App />)
|
||||
const rtl = render(<App />)
|
||||
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(
|
||||
rtl
|
||||
.getAllByRole('columnheader')
|
||||
.every(d => d.style.display === 'inline-block')
|
||||
).toBe(true)
|
||||
|
||||
expect(rtl.getAllByRole('row').every(d => d.style.display === 'flex')).toBe(
|
||||
true
|
||||
)
|
||||
|
||||
expect(
|
||||
rtl.getAllByRole('columnheader').map(d => d.style.width)
|
||||
).toStrictEqual([
|
||||
'550px',
|
||||
'850px',
|
||||
'250px',
|
||||
'300px',
|
||||
'300px',
|
||||
'150px',
|
||||
'200px',
|
||||
'200px',
|
||||
])
|
||||
})
|
||||
|
||||
186
src/plugin-hooks/tests/useColumnOrder.test.js
Normal file
186
src/plugin-hooks/tests/useColumnOrder.test.js
Normal file
@ -0,0 +1,186 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useColumnOrder } from '../useColumnOrder'
|
||||
|
||||
const data = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'bergevin',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
{
|
||||
firstName: 'jaylen',
|
||||
lastName: 'linsley',
|
||||
age: 26,
|
||||
visits: 99,
|
||||
status: 'In Relationship',
|
||||
progress: 70,
|
||||
},
|
||||
]
|
||||
|
||||
function shuffle(arr, mapping) {
|
||||
if (arr.length !== mapping.length) {
|
||||
throw new Error()
|
||||
}
|
||||
arr = [...arr]
|
||||
mapping = [...mapping]
|
||||
const shuffled = []
|
||||
while (arr.length) {
|
||||
shuffled.push(arr.splice([mapping.shift()], 1)[0])
|
||||
}
|
||||
return shuffled
|
||||
}
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
visibleColumns,
|
||||
prepareRow,
|
||||
setColumnOrder,
|
||||
state,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
},
|
||||
useColumnOrder
|
||||
)
|
||||
|
||||
const testColumnOrder = () => {
|
||||
setColumnOrder(
|
||||
shuffle(
|
||||
visibleColumns.map(d => d.id),
|
||||
[1, 4, 2, 0, 3, 5]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => testColumnOrder({})}>Randomize Columns</button>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map((headerGroup, i) => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.slice(0, 10).map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell, i) => {
|
||||
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<pre>
|
||||
<code>{JSON.stringify(state, null, 2)}</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
test('renders a column-orderable table', () => {
|
||||
const rtl = render(<App />)
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.textContent)).toEqual([
|
||||
'Name',
|
||||
'Info',
|
||||
'First Name',
|
||||
'Last Name',
|
||||
'Age',
|
||||
'Visits',
|
||||
'Status',
|
||||
'Profile Progress',
|
||||
])
|
||||
|
||||
fireEvent.click(rtl.getByText('Randomize Columns'))
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.textContent)).toEqual([
|
||||
'Name',
|
||||
'Info',
|
||||
'Name',
|
||||
'Info',
|
||||
'Last Name',
|
||||
'Profile Progress',
|
||||
'Visits',
|
||||
'First Name',
|
||||
'Age',
|
||||
'Status',
|
||||
])
|
||||
})
|
||||
@ -2,47 +2,9 @@ import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useExpanded } from '../useExpanded'
|
||||
import makeTestData from '../../../test-utils/makeTestData'
|
||||
|
||||
const makeData = () => [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'bergevin',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
{
|
||||
firstName: 'jaylen',
|
||||
lastName: 'linsley',
|
||||
age: 26,
|
||||
visits: 99,
|
||||
status: 'In Relationship',
|
||||
progress: 70,
|
||||
},
|
||||
]
|
||||
|
||||
const data = makeData()
|
||||
|
||||
data[0].subRows = makeData()
|
||||
data[0].subRows[0].subRows = makeData()
|
||||
data[0].subRows[0].subRows[0].subRows = makeData()
|
||||
const data = makeTestData(3, 3, 3)
|
||||
|
||||
function Table({ columns: userColumns, data, SubComponent }) {
|
||||
const {
|
||||
@ -51,8 +13,7 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
flatColumns,
|
||||
state: { expanded },
|
||||
visibleColumns,
|
||||
} = useTable(
|
||||
{
|
||||
columns: userColumns,
|
||||
@ -63,9 +24,6 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<pre>
|
||||
<code>{JSON.stringify({ expanded: expanded }, null, 2)}</code>
|
||||
</pre>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
@ -91,7 +49,7 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
</tr>
|
||||
{!row.subRows.length && row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={flatColumns.length}>
|
||||
<td colSpan={visibleColumns.length}>
|
||||
{SubComponent({ row })}
|
||||
</td>
|
||||
</tr>
|
||||
@ -119,43 +77,14 @@ function App() {
|
||||
}}
|
||||
onClick={() => row.toggleExpanded()}
|
||||
>
|
||||
{row.isExpanded ? '👇' : '👉'}
|
||||
{row.isExpanded ? 'Collapse' : 'Expand'} Row {row.id}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
Header: 'First Name',
|
||||
accessor: 'name',
|
||||
Cell: ({ row: { id } }) => `Row ${id}`,
|
||||
},
|
||||
],
|
||||
[]
|
||||
@ -165,51 +94,32 @@ function App() {
|
||||
<Table
|
||||
columns={columns}
|
||||
data={data}
|
||||
SubComponent={({ row }) => (
|
||||
<pre>
|
||||
<code>{JSON.stringify({ values: row.values }, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
SubComponent={({ row }) => <span>SubComponent: {row.id}</span>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
test('renders an expandable table', () => {
|
||||
const { getAllByText, asFragment } = render(<App />)
|
||||
const rtl = render(<App />)
|
||||
|
||||
let expandButtons = getAllByText('👉')
|
||||
rtl.getByText('Row 0')
|
||||
|
||||
const before = asFragment()
|
||||
fireEvent.click(rtl.getByText('Expand Row 0'))
|
||||
|
||||
fireEvent.click(expandButtons[0])
|
||||
rtl.getByText('Row 0.0')
|
||||
rtl.getByText('Row 0.1')
|
||||
rtl.getByText('Row 0.2')
|
||||
|
||||
const after1 = asFragment()
|
||||
fireEvent.click(rtl.getByText('Expand Row 0.1'))
|
||||
|
||||
expandButtons = getAllByText('👉')
|
||||
fireEvent.click(expandButtons[0])
|
||||
rtl.getByText('Row 0.1.2')
|
||||
|
||||
const after2 = asFragment()
|
||||
fireEvent.click(rtl.getByText('Expand Row 0.1.2'))
|
||||
|
||||
expandButtons = getAllByText('👉')
|
||||
fireEvent.click(expandButtons[0])
|
||||
rtl.getByText('SubComponent: 0.1.2')
|
||||
|
||||
const after3 = asFragment()
|
||||
fireEvent.click(rtl.getByText('Collapse Row 0'))
|
||||
|
||||
expandButtons = getAllByText('👉')
|
||||
fireEvent.click(expandButtons[0])
|
||||
|
||||
const after4 = asFragment()
|
||||
|
||||
expandButtons = getAllByText('👇')
|
||||
expandButtons.reverse().forEach(button => {
|
||||
fireEvent.click(button)
|
||||
})
|
||||
|
||||
const after5 = asFragment()
|
||||
|
||||
expect(before).toMatchDiffSnapshot(after1)
|
||||
expect(after1).toMatchDiffSnapshot(after2)
|
||||
expect(after2).toMatchDiffSnapshot(after3)
|
||||
expect(after3).toMatchDiffSnapshot(after4)
|
||||
expect(after4).toMatchDiffSnapshot(after5)
|
||||
expect(rtl.queryByText('SubComponent: 0.1.2')).toBe(null)
|
||||
rtl.getByText('Expand Row 0')
|
||||
})
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useFilters } from '../useFilters'
|
||||
import { useGlobalFilter } from '../useGlobalFilter'
|
||||
|
||||
const data = [
|
||||
const makeData = () => [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
@ -52,79 +52,8 @@ const defaultColumn = {
|
||||
),
|
||||
}
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
flatColumns,
|
||||
state,
|
||||
setGlobalFilter,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
},
|
||||
useFilters,
|
||||
useGlobalFilter
|
||||
)
|
||||
|
||||
return (
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>
|
||||
{column.render('Header')}
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<th
|
||||
colSpan={flatColumns.length}
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
value={state.globalFilter || ''}
|
||||
onChange={e => {
|
||||
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||
}}
|
||||
placeholder={`Global search...`}
|
||||
style={{
|
||||
fontSize: '1.1rem',
|
||||
border: '0',
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [data, setData] = React.useState(makeData)
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -165,38 +94,152 @@ function App() {
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
test('renders a filterable table', () => {
|
||||
const { getAllByPlaceholderText, getByPlaceholderText, asFragment } = render(
|
||||
<App />
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
visibleColumns,
|
||||
state,
|
||||
setGlobalFilter,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
},
|
||||
useFilters,
|
||||
useGlobalFilter
|
||||
)
|
||||
|
||||
const globalFilterInput = getByPlaceholderText('Global search...')
|
||||
const filterInputs = getAllByPlaceholderText('Search...')
|
||||
const reset = () => setData(makeData())
|
||||
|
||||
const beforeFilter = asFragment()
|
||||
return (
|
||||
<>
|
||||
<button onClick={reset}>Reset Data</button>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>
|
||||
{column.render('Header')}
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<th
|
||||
colSpan={visibleColumns.length}
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
value={state.globalFilter || ''}
|
||||
onChange={e => {
|
||||
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||
}}
|
||||
placeholder={`Global search...`}
|
||||
style={{
|
||||
fontSize: '1.1rem',
|
||||
border: '0',
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
test('renders a filterable table', async () => {
|
||||
const rendered = render(<App />)
|
||||
|
||||
const resetButton = rendered.getByText('Reset Data')
|
||||
const globalFilterInput = rendered.getByPlaceholderText('Global search...')
|
||||
const filterInputs = rendered.getAllByPlaceholderText('Search...')
|
||||
|
||||
fireEvent.change(filterInputs[1], { target: { value: 'l' } })
|
||||
|
||||
const afterFilter1 = asFragment()
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: tanner', 'firstName: jaylen'])
|
||||
|
||||
fireEvent.change(filterInputs[1], { target: { value: 'er' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: derek', 'firstName: joe'])
|
||||
|
||||
const afterFilter2 = asFragment()
|
||||
fireEvent.change(filterInputs[2], { target: { value: 'nothing' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([])
|
||||
|
||||
fireEvent.change(filterInputs[1], { target: { value: '' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([])
|
||||
|
||||
const afterFilter3 = asFragment()
|
||||
fireEvent.change(filterInputs[2], { target: { value: '' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([
|
||||
'firstName: tanner',
|
||||
'firstName: derek',
|
||||
'firstName: joe',
|
||||
'firstName: jaylen',
|
||||
])
|
||||
|
||||
fireEvent.change(globalFilterInput, { target: { value: 'li' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: tanner', 'firstName: joe', 'firstName: jaylen'])
|
||||
|
||||
const afterFilter4 = asFragment()
|
||||
|
||||
expect(beforeFilter).toMatchSnapshot()
|
||||
expect(afterFilter1).toMatchSnapshot()
|
||||
expect(afterFilter2).toMatchSnapshot()
|
||||
expect(afterFilter3).toMatchSnapshot()
|
||||
expect(afterFilter4).toMatchSnapshot()
|
||||
fireEvent.click(resetButton)
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([
|
||||
'firstName: tanner',
|
||||
'firstName: derek',
|
||||
'firstName: joe',
|
||||
'firstName: jaylen',
|
||||
])
|
||||
})
|
||||
|
||||
157
src/plugin-hooks/tests/useFlexLayout.test.js
Normal file
157
src/plugin-hooks/tests/useFlexLayout.test.js
Normal file
@ -0,0 +1,157 @@
|
||||
import React from 'react'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useFlexLayout } from '../useFlexLayout'
|
||||
import { render } from '../../../test-utils/react-testing'
|
||||
|
||||
const data = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 30,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'bergevin',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
]
|
||||
|
||||
const defaultColumn = {
|
||||
Cell: ({ cell: { value }, column: { id } }) => `${id}: ${value}`,
|
||||
width: 200,
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
}
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
},
|
||||
useFlexLayout
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...getTableProps()} className="table">
|
||||
<div>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="row">
|
||||
{headerGroup.headers.map(column => (
|
||||
<div {...column.getHeaderProps()} className="cell header">
|
||||
{column.render('Header')}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<div {...row.getRowProps()} className="row">
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<div {...cell.getCellProps()} className="cell">
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
width: 350,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
minWidth: 300,
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
maxWidth: 150,
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
test('renders a table', () => {
|
||||
const rendered = render(<App />)
|
||||
|
||||
const [headerRow, firstRow] = rendered.queryAllByRole('row')
|
||||
|
||||
expect(headerRow.getAttribute('style')).toEqual(
|
||||
'display: flex; flex: 1 0 auto; min-width: 800px;'
|
||||
)
|
||||
|
||||
expect(
|
||||
Array.from(firstRow.children).map(d => d.getAttribute('style'))
|
||||
).toEqual([
|
||||
'box-sizing: border-box; flex: 250 0 auto; min-width: 100px; width: 250px;',
|
||||
'box-sizing: border-box; flex: 300 0 auto; min-width: 100px; width: 300px;',
|
||||
'box-sizing: border-box; flex: 300 0 auto; min-width: 300px; width: 300px;',
|
||||
'box-sizing: border-box; flex: 150 0 auto; min-width: 100px; width: 150px;',
|
||||
'box-sizing: border-box; flex: 200 0 auto; min-width: 100px; width: 200px;',
|
||||
'box-sizing: border-box; flex: 200 0 auto; min-width: 100px; width: 200px;',
|
||||
])
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useGroupBy } from '../useGroupBy'
|
||||
import { useExpanded } from '../useExpanded'
|
||||
@ -29,6 +29,14 @@ const data = [
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'dirt',
|
||||
age: 20,
|
||||
visits: 5,
|
||||
status: 'Complicated',
|
||||
progress: 97,
|
||||
},
|
||||
{
|
||||
firstName: 'jaylen',
|
||||
lastName: 'linsley',
|
||||
@ -82,7 +90,7 @@ function Table({ columns, data }) {
|
||||
{column.canGroupBy ? (
|
||||
// If the column can be grouped, let's add a toggle
|
||||
<span {...column.getGroupByToggleProps()}>
|
||||
{column.isGrouped ? '🛑' : '👊'}
|
||||
{column.isGrouped ? 'Ungroup' : 'Group'} {column.id}
|
||||
</span>
|
||||
) : null}
|
||||
{column.render('Header')}
|
||||
@ -113,7 +121,7 @@ function Table({ columns, data }) {
|
||||
</>
|
||||
) : cell.isAggregated ? (
|
||||
cell.render('Aggregated')
|
||||
) : cell.isRepeatedValue ? null : (
|
||||
) : cell.isPlaceholder ? null : (
|
||||
cell.render('Cell')
|
||||
)}
|
||||
</td>
|
||||
@ -127,11 +135,14 @@ function Table({ columns, data }) {
|
||||
)
|
||||
}
|
||||
|
||||
function roundedMedian(values) {
|
||||
let min = values[0] || ''
|
||||
let max = values[0] || ''
|
||||
// This is a custom aggregator that
|
||||
// takes in an array of leaf values and
|
||||
// returns the rounded median
|
||||
function roundedMedian(leafValues) {
|
||||
let min = leafValues[0] || 0
|
||||
let max = leafValues[0] || 0
|
||||
|
||||
values.forEach(value => {
|
||||
leafValues.forEach(value => {
|
||||
min = Math.min(min, value)
|
||||
max = Math.max(max, value)
|
||||
})
|
||||
@ -148,14 +159,16 @@ function App() {
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
aggregate: ['sum', 'count'],
|
||||
Aggregated: ({ cell: { value } }) => `${value} Names`,
|
||||
aggregate: 'count',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`First Name Aggregated: ${value} Names`,
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
aggregate: ['sum', 'uniqueCount'],
|
||||
Aggregated: ({ cell: { value } }) => `${value} Unique Names`,
|
||||
aggregate: 'uniqueCount',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Last Name Aggregated: ${value} Unique Names`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -166,23 +179,62 @@ function App() {
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: 'average',
|
||||
Aggregated: ({ cell: { value } }) => `${value} (avg)`,
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Age Aggregated: ${value} (avg)`,
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: 'sum',
|
||||
Aggregated: ({ cell: { value } }) => `${value} (total)`,
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Visits Aggregated: ${value} (total)`,
|
||||
},
|
||||
{
|
||||
Header: 'Min Visits',
|
||||
id: 'minVisits',
|
||||
accessor: 'visits',
|
||||
aggregate: 'min',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Visits Aggregated: ${value} (min)`,
|
||||
},
|
||||
{
|
||||
Header: 'Max Visits',
|
||||
id: 'maxVisits',
|
||||
accessor: 'visits',
|
||||
aggregate: 'max',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Visits Aggregated: ${value} (max)`,
|
||||
},
|
||||
{
|
||||
Header: 'Min/Max Visits',
|
||||
id: 'minMaxVisits',
|
||||
accessor: 'visits',
|
||||
aggregate: 'minMax',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Visits Aggregated: ${value} (minMax)`,
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
aggregate: 'unique',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Visits Aggregated: ${value.join(', ')} (unique)`,
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
Header: 'Profile Progress (Median)',
|
||||
accessor: 'progress',
|
||||
id: 'progress',
|
||||
aggregate: 'median',
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Process Aggregated: ${value} (median)`,
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress (Rounded Median)',
|
||||
accessor: 'progress',
|
||||
id: 'progressRounded',
|
||||
aggregate: roundedMedian,
|
||||
Aggregated: ({ cell: { value } }) => `${value} (med)`,
|
||||
Aggregated: ({ cell: { value } }) =>
|
||||
`Process Aggregated: ${value} (rounded median)`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -194,20 +246,26 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a groupable table', () => {
|
||||
const { getAllByText, asFragment } = render(<App />)
|
||||
const rendered = render(<App />)
|
||||
|
||||
const groupByButtons = getAllByText('👊')
|
||||
fireEvent.click(rendered.getByText('Group lastName'))
|
||||
|
||||
const beforeGrouping = asFragment()
|
||||
rendered.getByText('lastName: linsley (2)')
|
||||
|
||||
fireEvent.click(groupByButtons[1])
|
||||
fireEvent.click(rendered.getByText('Group visits'))
|
||||
|
||||
const afterGrouping1 = asFragment()
|
||||
fireEvent.click(rendered.getByText('Ungroup lastName'))
|
||||
|
||||
fireEvent.click(groupByButtons[3])
|
||||
rendered.getByText('visits: 100 (1)')
|
||||
|
||||
const afterGrouping2 = asFragment()
|
||||
fireEvent.click(rendered.getByText('Ungroup visits'))
|
||||
|
||||
expect(beforeGrouping).toMatchDiffSnapshot(afterGrouping1)
|
||||
expect(afterGrouping1).toMatchDiffSnapshot(afterGrouping2)
|
||||
fireEvent.click(rendered.getByText('Group firstName'))
|
||||
|
||||
rendered.getByText('firstName: tanner (1)')
|
||||
|
||||
rendered.debugDiff(false)
|
||||
fireEvent.click(rendered.getByText('Group age'))
|
||||
|
||||
rendered.getByText('Last Name Aggregated: 2 Unique Names')
|
||||
})
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { usePagination } from '../usePagination'
|
||||
|
||||
const data = [...new Array(100)].map((d, i) => ({
|
||||
firstName: 'tanner ' + (i + 1),
|
||||
const data = [...new Array(1000)].map((d, i) => ({
|
||||
firstName: `tanner ${i + 1}`,
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
@ -100,6 +100,7 @@ function Table({ columns, data }) {
|
||||
onChange={e => {
|
||||
setPageSize(Number(e.target.value))
|
||||
}}
|
||||
data-testid="page-size-select"
|
||||
>
|
||||
{[10, 20, 30, 40, 50].map(pageSize => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
@ -157,23 +158,30 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a paginated table', () => {
|
||||
const { getByText, asFragment } = render(<App />)
|
||||
const rendered = render(<App />)
|
||||
|
||||
const fragment1 = asFragment()
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 21')
|
||||
|
||||
fireEvent.click(getByText('>'))
|
||||
fireEvent.click(rendered.getByText('>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 31')
|
||||
|
||||
const fragment2 = asFragment()
|
||||
fireEvent.click(rendered.getByText('>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 41')
|
||||
|
||||
fireEvent.click(getByText('>'))
|
||||
fireEvent.click(rendered.getByText('>>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 991')
|
||||
|
||||
const fragment3 = asFragment()
|
||||
fireEvent.click(rendered.getByText('<<'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 1')
|
||||
|
||||
fireEvent.click(getByText('>>'))
|
||||
fireEvent.change(rendered.getByTestId('page-size-select'), {
|
||||
target: { value: 30 },
|
||||
})
|
||||
|
||||
const fragment4 = asFragment()
|
||||
|
||||
expect(fragment1).toMatchDiffSnapshot(fragment2)
|
||||
expect(fragment2).toMatchDiffSnapshot(fragment3)
|
||||
expect(fragment3).toMatchDiffSnapshot(fragment4)
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.reverse()[0].children[0].textContent
|
||||
).toEqual('tanner 30')
|
||||
})
|
||||
|
||||
234
src/plugin-hooks/tests/useResizeColumns.test.js
Normal file
234
src/plugin-hooks/tests/useResizeColumns.test.js
Normal file
@ -0,0 +1,234 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useBlockLayout } from '../useBlockLayout'
|
||||
import { useResizeColumns } from '../useResizeColumns'
|
||||
|
||||
const data = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'bergevin',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
]
|
||||
function Table({ columns, data }) {
|
||||
const defaultColumn = React.useMemo(
|
||||
() => ({
|
||||
minWidth: 30,
|
||||
width: 150,
|
||||
maxWidth: 400,
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
},
|
||||
useBlockLayout,
|
||||
useResizeColumns
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...getTableProps()} className="table">
|
||||
<div>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||
{headerGroup.headers.map(column => (
|
||||
<div {...column.getHeaderProps()} className="th">
|
||||
{column.render('Header')}
|
||||
{/* Use column.getResizerProps to hook up the events correctly */}
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={`resizer${column.isResizing ? ' isResizing' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div {...getTableBodyProps()}>
|
||||
{rows.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<div {...row.getRowProps()} className="tr">
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<div {...cell.getCellProps()} className="td">
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
const start = 20
|
||||
const move = 100
|
||||
const end = 100
|
||||
|
||||
const sizesBefore = [
|
||||
'300px',
|
||||
'410px',
|
||||
'150px',
|
||||
'150px',
|
||||
'50px',
|
||||
'60px',
|
||||
'150px',
|
||||
'150px',
|
||||
]
|
||||
|
||||
const sizesAfter = [
|
||||
'300px',
|
||||
'490px',
|
||||
'150px',
|
||||
'150px',
|
||||
'59.75609756097561px',
|
||||
'71.70731707317073px',
|
||||
'179.26829268292684px',
|
||||
'179.26829268292684px',
|
||||
]
|
||||
|
||||
test('table can be resized by a mouse', () => {
|
||||
const rtl = render(<App />)
|
||||
|
||||
const infoResizer = rtl
|
||||
.getAllByRole('separator')
|
||||
.find(d => d.previousSibling.textContent === 'Info')
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesBefore
|
||||
)
|
||||
|
||||
fireEvent.mouseDown(infoResizer, { clientX: start })
|
||||
fireEvent.mouseMove(infoResizer, { clientX: move })
|
||||
fireEvent.mouseUp(infoResizer, { clientX: end })
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesAfter
|
||||
)
|
||||
})
|
||||
|
||||
test('table can be resized by a touch device', () => {
|
||||
const rtl = render(<App />)
|
||||
|
||||
const infoResizer = rtl
|
||||
.getAllByRole('separator')
|
||||
.find(d => d.previousSibling.textContent === 'Info')
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesBefore
|
||||
)
|
||||
|
||||
fireEvent.touchStart(infoResizer, { touches: [{ clientX: start }] })
|
||||
fireEvent.touchMove(infoResizer, { touches: [{ clientX: move }] })
|
||||
fireEvent.touchEnd(infoResizer, { touches: [{ clientX: end }] })
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesAfter
|
||||
)
|
||||
})
|
||||
|
||||
test('table can not be resized with multiple touches', () => {
|
||||
const rtl = render(<App />)
|
||||
|
||||
const infoResizer = rtl
|
||||
.getAllByRole('separator')
|
||||
.find(d => d.previousSibling.textContent === 'Info')
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesBefore
|
||||
)
|
||||
|
||||
fireEvent.touchStart(infoResizer, {
|
||||
touches: [{ clientX: start }, { clientX: start }],
|
||||
})
|
||||
fireEvent.touchMove(infoResizer, {
|
||||
touches: [{ clientX: move }, { clientX: move }],
|
||||
})
|
||||
fireEvent.touchEnd(infoResizer, {
|
||||
touches: [{ clientX: end }, { clientX: end }],
|
||||
})
|
||||
|
||||
expect(rtl.getAllByRole('columnheader').map(d => d.style.width)).toEqual(
|
||||
sizesBefore
|
||||
)
|
||||
})
|
||||
@ -84,7 +84,7 @@ function Table({ columns, data }) {
|
||||
useRowSelect,
|
||||
useExpanded,
|
||||
hooks => {
|
||||
hooks.flatColumns.push(columns => [
|
||||
hooks.visibleColumns.push(columns => [
|
||||
// Let's make a column for selection
|
||||
{
|
||||
id: 'selection',
|
||||
@ -142,12 +142,6 @@ function Table({ columns, data }) {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Selected Rows: {Object.keys(selectedRowIds).length}</p>
|
||||
<pre>
|
||||
<code>
|
||||
{JSON.stringify({ selectedRowIds: selectedRowIds }, null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -170,11 +164,13 @@ function App() {
|
||||
() => [
|
||||
{
|
||||
id: 'selectedStatus',
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
Row {row.id} {row.isSelected ? 'Selected' : 'Not Selected'}
|
||||
</div>
|
||||
),
|
||||
Cell: ({ row }) =>
|
||||
row.isSelected ? (
|
||||
<div>
|
||||
<div>Selected</div>
|
||||
<div>Row {row.id}</div>
|
||||
</div>
|
||||
) : null,
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
@ -218,43 +214,43 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a table with selectable rows', () => {
|
||||
const { getByLabelText, getAllByLabelText, asFragment } = render(<App />)
|
||||
const rtl = render(<App />)
|
||||
|
||||
const fragment1 = asFragment()
|
||||
fireEvent.click(rtl.getByLabelText('Select All'))
|
||||
|
||||
fireEvent.click(getByLabelText('Select All'))
|
||||
expect(rtl.getAllByText('Selected').length).toBe(24)
|
||||
|
||||
const fragment2 = asFragment()
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[2])
|
||||
|
||||
fireEvent.click(getByLabelText('Select All'))
|
||||
expect(rtl.queryAllByText('Selected').length).toBe(23)
|
||||
|
||||
const fragment3 = asFragment()
|
||||
fireEvent.click(rtl.getByLabelText('Select All'))
|
||||
|
||||
fireEvent.click(getAllByLabelText('Select Row')[0])
|
||||
fireEvent.click(getAllByLabelText('Select Row')[2])
|
||||
expect(rtl.queryAllByText('Selected').length).toBe(24)
|
||||
|
||||
const fragment4 = asFragment()
|
||||
fireEvent.click(rtl.getByLabelText('Select All'))
|
||||
|
||||
fireEvent.click(getAllByLabelText('Select Row')[2])
|
||||
expect(rtl.queryAllByText('Selected').length).toBe(0)
|
||||
|
||||
const fragment5 = asFragment()
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[0])
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[2])
|
||||
|
||||
fireEvent.click(getAllByLabelText('Select Row')[3])
|
||||
rtl.getByText('Row 0')
|
||||
rtl.getByText('Row 2')
|
||||
|
||||
const fragment6 = asFragment()
|
||||
fireEvent.click(getAllByLabelText('Select Row')[4])
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[2])
|
||||
|
||||
const fragment7 = asFragment()
|
||||
expect(rtl.queryByText('Row 2')).toBeNull()
|
||||
|
||||
fireEvent.click(getAllByLabelText('Select Row')[4])
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[3])
|
||||
|
||||
const fragment8 = asFragment()
|
||||
rtl.queryByText('Row 3')
|
||||
|
||||
expect(fragment1).toMatchDiffSnapshot(fragment2)
|
||||
expect(fragment2).toMatchDiffSnapshot(fragment3)
|
||||
expect(fragment3).toMatchDiffSnapshot(fragment4)
|
||||
expect(fragment4).toMatchDiffSnapshot(fragment5)
|
||||
expect(fragment5).toMatchDiffSnapshot(fragment6)
|
||||
expect(fragment6).toMatchDiffSnapshot(fragment7)
|
||||
expect(fragment7).toMatchDiffSnapshot(fragment8)
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[4])
|
||||
|
||||
rtl.queryByText('Row 4')
|
||||
|
||||
fireEvent.click(rtl.getAllByLabelText('Select Row')[4])
|
||||
|
||||
expect(rtl.queryByText('Row 4')).toBeNull()
|
||||
})
|
||||
|
||||
185
src/plugin-hooks/tests/useRowState.test.js
Normal file
185
src/plugin-hooks/tests/useRowState.test.js
Normal file
@ -0,0 +1,185 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useRowState } from '../useRowState'
|
||||
import { useGlobalFilter } from '../useGlobalFilter'
|
||||
|
||||
const data = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'joe',
|
||||
lastName: 'bergevin',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Complicated',
|
||||
progress: 10,
|
||||
},
|
||||
{
|
||||
firstName: 'jaylen',
|
||||
lastName: 'linsley',
|
||||
age: 26,
|
||||
visits: 99,
|
||||
status: 'In Relationship',
|
||||
progress: 70,
|
||||
},
|
||||
]
|
||||
|
||||
const defaultColumn = {
|
||||
Cell: ({ column, cell, row }) => (
|
||||
<div>
|
||||
Row {row.id} Cell {column.id} Count {cell.state.count}{' '}
|
||||
<button
|
||||
onClick={() => cell.setState(old => ({ ...old, count: old.count + 1 }))}
|
||||
>
|
||||
Row {row.id} Cell {column.id} Toggle
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
initialRowStateAccessor: () => ({ count: 0 }),
|
||||
initialCellStateAccessor: () => ({ count: 0 }),
|
||||
},
|
||||
useRowState,
|
||||
useGlobalFilter
|
||||
)
|
||||
|
||||
return (
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
<td>
|
||||
<pre>Row Count {row.state.count}</pre>
|
||||
<button
|
||||
onClick={() =>
|
||||
row.setState(old => ({
|
||||
...old,
|
||||
count: old.count + 1,
|
||||
}))
|
||||
}
|
||||
>
|
||||
Row {row.id} Toggle
|
||||
</button>
|
||||
</td>
|
||||
{row.cells.map(cell => (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
test('renders a filterable table', () => {
|
||||
const rendered = render(<App />)
|
||||
|
||||
fireEvent.click(rendered.getByText('Row 1 Toggle'))
|
||||
fireEvent.click(rendered.getByText('Row 1 Toggle'))
|
||||
|
||||
rendered.getByText('Row Count 2')
|
||||
|
||||
fireEvent.click(rendered.getByText('Row 1 Cell firstName Toggle'))
|
||||
|
||||
rendered.getByText('Row 1 Cell firstName Count 1')
|
||||
|
||||
fireEvent.click(rendered.getByText('Row 2 Cell lastName Toggle'))
|
||||
fireEvent.click(rendered.getByText('Row 2 Cell lastName Toggle'))
|
||||
|
||||
rendered.getByText('Row 2 Cell lastName Count 2')
|
||||
|
||||
fireEvent.click(rendered.getByText('Row 3 Cell age Toggle'))
|
||||
fireEvent.click(rendered.getByText('Row 3 Cell age Toggle'))
|
||||
fireEvent.click(rendered.getByText('Row 3 Cell age Toggle'))
|
||||
|
||||
rendered.getByText('Row 3 Cell age Count 3')
|
||||
|
||||
fireEvent.click(rendered.getByText('Row 1 Toggle'))
|
||||
fireEvent.click(rendered.getByText('Row 1 Toggle'))
|
||||
|
||||
rendered.getByText('Row Count 4')
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useSortBy } from '../useSortBy'
|
||||
|
||||
@ -10,7 +10,7 @@ const data = [
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
@ -61,9 +61,9 @@ function Table({ columns, data }) {
|
||||
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
|
||||
{column.render('Header')}
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}
|
||||
</span>
|
||||
{column.isSorted
|
||||
? (column.isSortedDesc ? ' 🔽' : ' 🔼') + column.sortedIndex
|
||||
: ''}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
@ -130,18 +130,42 @@ function App() {
|
||||
}
|
||||
|
||||
test('renders a sortable table', () => {
|
||||
const { getByText, asFragment } = render(<App />)
|
||||
const rendered = render(<App />)
|
||||
|
||||
const beforeSort = asFragment()
|
||||
fireEvent.click(rendered.getByText('First Name'))
|
||||
rendered.getByText('First Name 🔼0')
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.map(d => d.children[0].textContent)
|
||||
).toEqual(['firstName: derek', 'firstName: joe', 'firstName: tanner'])
|
||||
|
||||
fireEvent.click(getByText('First Name'))
|
||||
fireEvent.click(rendered.getByText('First Name 🔼0'))
|
||||
rendered.getByText('First Name 🔽0')
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.map(d => d.children[0].textContent)
|
||||
).toEqual(['firstName: tanner', 'firstName: joe', 'firstName: derek'])
|
||||
|
||||
const afterSort1 = asFragment()
|
||||
fireEvent.click(rendered.getByText('Profile Progress'))
|
||||
rendered.getByText('Profile Progress 🔼0')
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.map(d => d.children[0].textContent)
|
||||
).toEqual(['firstName: joe', 'firstName: tanner', 'firstName: derek'])
|
||||
|
||||
fireEvent.click(getByText('First Name'))
|
||||
|
||||
const afterSort2 = asFragment()
|
||||
|
||||
expect(beforeSort).toMatchDiffSnapshot(afterSort1)
|
||||
expect(afterSort1).toMatchDiffSnapshot(afterSort2)
|
||||
fireEvent.click(rendered.getByText('First Name'), { shiftKey: true })
|
||||
rendered.getByText('Profile Progress 🔼0')
|
||||
rendered.getByText('First Name 🔼1')
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.map(d => d.children[0].textContent)
|
||||
).toEqual(['firstName: joe', 'firstName: derek', 'firstName: tanner'])
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ensurePluginOrder } from '../utils'
|
||||
import { ensurePluginOrder } from '../publicUtils'
|
||||
|
||||
const cellStyles = {
|
||||
position: 'absolute',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import { functionalUpdate, actions } from '../utils'
|
||||
import { functionalUpdate, actions } from '../publicUtils'
|
||||
|
||||
// Actions
|
||||
actions.resetColumnOrder = 'resetColumnOrder'
|
||||
@ -8,10 +8,10 @@ actions.setColumnOrder = 'setColumnOrder'
|
||||
|
||||
export const useColumnOrder = hooks => {
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.flatColumnsDeps.push((deps, { instance }) => {
|
||||
hooks.visibleColumnsDeps.push((deps, { instance }) => {
|
||||
return [...deps, instance.state.columnOrder]
|
||||
})
|
||||
hooks.flatColumns.push(flatColumns)
|
||||
hooks.visibleColumns.push(visibleColumns)
|
||||
hooks.useInstance.push(useInstance)
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
}
|
||||
|
||||
function flatColumns(
|
||||
function visibleColumns(
|
||||
columns,
|
||||
{
|
||||
instance: {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
|
||||
import { expandRows } from '../utils'
|
||||
|
||||
import {
|
||||
actions,
|
||||
makePropGetter,
|
||||
expandRows,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
} from '../utils'
|
||||
import { useConsumeHookGetter, functionalUpdate } from '../publicUtils'
|
||||
actions,
|
||||
functionalUpdate,
|
||||
useMountedLayoutEffect,
|
||||
makePropGetter,
|
||||
} from '../publicUtils'
|
||||
|
||||
// Actions
|
||||
actions.toggleExpanded = 'toggleExpanded'
|
||||
@ -19,6 +20,7 @@ export const useExpanded = hooks => {
|
||||
hooks.getExpandedToggleProps = [defaultGetExpandedToggleProps]
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.useInstance.push(useInstance)
|
||||
hooks.prepareRow.push(prepareRow)
|
||||
}
|
||||
|
||||
useExpanded.pluginName = 'useExpanded'
|
||||
@ -61,7 +63,7 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
|
||||
if (action.type === actions.toggleExpanded) {
|
||||
const { id, expanded: setExpanded } = action
|
||||
const { id, value: setExpanded } = action
|
||||
const exists = state.expanded[id]
|
||||
|
||||
const shouldExist =
|
||||
@ -94,7 +96,6 @@ function useInstance(instance) {
|
||||
manualExpandedKey = 'expanded',
|
||||
paginateExpandedRows = true,
|
||||
expandSubRows = true,
|
||||
hooks,
|
||||
autoResetExpanded = true,
|
||||
state: { expanded },
|
||||
dispatch,
|
||||
@ -109,27 +110,10 @@ function useInstance(instance) {
|
||||
}
|
||||
}, [dispatch, data])
|
||||
|
||||
const toggleExpanded = (id, expanded) => {
|
||||
dispatch({ type: actions.toggleExpanded, id, expanded })
|
||||
const toggleExpanded = (id, value) => {
|
||||
dispatch({ type: actions.toggleExpanded, id, value })
|
||||
}
|
||||
|
||||
// use reference to avoid memory leak in #1608
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
const getExpandedTogglePropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getExpandedToggleProps'
|
||||
)
|
||||
|
||||
hooks.prepareRow.push(row => {
|
||||
row.toggleExpanded = set => instance.toggleExpanded(row.id, set)
|
||||
|
||||
row.getExpandedToggleProps = makePropGetter(getExpandedTogglePropsHooks(), {
|
||||
instance: getInstance(),
|
||||
row,
|
||||
})
|
||||
})
|
||||
|
||||
const expandedRows = React.useMemo(() => {
|
||||
if (paginateExpandedRows) {
|
||||
return expandRows(rows, { manualExpandedKey, expanded, expandSubRows })
|
||||
@ -151,6 +135,18 @@ function useInstance(instance) {
|
||||
})
|
||||
}
|
||||
|
||||
function prepareRow(row, { instance: { getHooks }, instance }) {
|
||||
row.toggleExpanded = set => instance.toggleExpanded(row.id, set)
|
||||
|
||||
row.getExpandedToggleProps = makePropGetter(
|
||||
getHooks().getExpandedToggleProps,
|
||||
{
|
||||
instance,
|
||||
row,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function findExpandedDepth(expanded) {
|
||||
let maxDepth = 0
|
||||
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
actions,
|
||||
getFirstDefined,
|
||||
getFilterMethod,
|
||||
useMountedLayoutEffect,
|
||||
functionalUpdate,
|
||||
useGetLatest,
|
||||
shouldAutoRemoveFilter,
|
||||
} from '../utils'
|
||||
|
||||
import {
|
||||
actions,
|
||||
useGetLatest,
|
||||
functionalUpdate,
|
||||
useMountedLayoutEffect,
|
||||
} from '../publicUtils'
|
||||
|
||||
import * as filterTypes from '../filterTypes'
|
||||
|
||||
// Actions
|
||||
@ -40,9 +44,9 @@ function reducer(state, action, previousState, instance) {
|
||||
|
||||
if (action.type === actions.setFilter) {
|
||||
const { columnId, filterValue } = action
|
||||
const { flatColumns, userFilterTypes } = instance
|
||||
const { allColumns, userFilterTypes } = instance
|
||||
|
||||
const column = flatColumns.find(d => d.id === columnId)
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(
|
||||
@ -91,13 +95,13 @@ function reducer(state, action, previousState, instance) {
|
||||
|
||||
if (action.type === actions.setAllFilters) {
|
||||
const { filters } = action
|
||||
const { flatColumns, filterTypes: userFilterTypes } = instance
|
||||
const { allColumns, filterTypes: userFilterTypes } = instance
|
||||
|
||||
return {
|
||||
...state,
|
||||
// Filter out undefined values
|
||||
filters: functionalUpdate(filters, state.filters).filter(filter => {
|
||||
const column = flatColumns.find(d => d.id === filter.id)
|
||||
const column = allColumns.find(d => d.id === filter.id)
|
||||
const filterMethod = getFilterMethod(
|
||||
column.filter,
|
||||
userFilterTypes || {},
|
||||
@ -118,7 +122,7 @@ function useInstance(instance) {
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
filterTypes: userFilterTypes,
|
||||
manualFilters,
|
||||
defaultCanFilter = false,
|
||||
@ -139,7 +143,7 @@ function useInstance(instance) {
|
||||
})
|
||||
}
|
||||
|
||||
flatColumns.forEach(column => {
|
||||
allColumns.forEach(column => {
|
||||
const {
|
||||
id,
|
||||
accessor,
|
||||
@ -179,7 +183,7 @@ function useInstance(instance) {
|
||||
filteredRows = filters.reduce(
|
||||
(filteredSoFar, { id: columnId, value: filterValue }) => {
|
||||
// Find the filters column
|
||||
const column = flatColumns.find(d => d.id === columnId)
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
|
||||
if (!column) {
|
||||
return filteredSoFar
|
||||
@ -237,12 +241,12 @@ function useInstance(instance) {
|
||||
}
|
||||
|
||||
return [filterRows(rows), filteredFlatRows]
|
||||
}, [manualFilters, filters, rows, flatRows, flatColumns, userFilterTypes])
|
||||
}, [manualFilters, filters, rows, flatRows, allColumns, userFilterTypes])
|
||||
|
||||
React.useMemo(() => {
|
||||
// Now that each filtered column has it's partially filtered rows,
|
||||
// lets assign the final filtered rows to all of the other columns
|
||||
const nonFilteredColumns = flatColumns.filter(
|
||||
const nonFilteredColumns = allColumns.filter(
|
||||
column => !filters.find(d => d.id === column.id)
|
||||
)
|
||||
|
||||
@ -252,7 +256,7 @@ function useInstance(instance) {
|
||||
column.preFilteredRows = filteredRows
|
||||
column.filteredRows = filteredRows
|
||||
})
|
||||
}, [filteredRows, filters, flatColumns])
|
||||
}, [filteredRows, filters, allColumns])
|
||||
|
||||
const getAutoResetFilters = useGetLatest(autoResetFilters)
|
||||
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
import { getFilterMethod, shouldAutoRemoveFilter } from '../utils'
|
||||
|
||||
import {
|
||||
actions,
|
||||
getFilterMethod,
|
||||
useMountedLayoutEffect,
|
||||
functionalUpdate,
|
||||
useGetLatest,
|
||||
shouldAutoRemoveFilter,
|
||||
ensurePluginOrder,
|
||||
} from '../utils'
|
||||
useGetLatest,
|
||||
} from '../publicUtils'
|
||||
|
||||
import * as filterTypes from '../filterTypes'
|
||||
|
||||
// Actions
|
||||
@ -60,7 +61,7 @@ function useInstance(instance) {
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
filterTypes: userFilterTypes,
|
||||
globalFilter,
|
||||
manualGlobalFilter,
|
||||
@ -106,7 +107,7 @@ function useInstance(instance) {
|
||||
const filterRows = filteredRows => {
|
||||
return filterMethod(
|
||||
filteredRows,
|
||||
flatColumns.map(d => d.id),
|
||||
allColumns.map(d => d.id),
|
||||
globalFilterValue
|
||||
).map(row => {
|
||||
filteredFlatRows.push(row)
|
||||
@ -128,7 +129,7 @@ function useInstance(instance) {
|
||||
userFilterTypes,
|
||||
rows,
|
||||
flatRows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
globalFilterValue,
|
||||
])
|
||||
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
import * as aggregations from '../aggregations'
|
||||
|
||||
import { getFirstDefined, flattenBy } from '../utils'
|
||||
|
||||
import {
|
||||
actions,
|
||||
makePropGetter,
|
||||
defaultGroupByFn,
|
||||
getFirstDefined,
|
||||
ensurePluginOrder,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
} from '../utils'
|
||||
import { useConsumeHookGetter } from '../publicUtils'
|
||||
} from '../publicUtils'
|
||||
|
||||
// Actions
|
||||
actions.resetGroupBy = 'resetGroupBy'
|
||||
@ -19,12 +20,13 @@ actions.toggleGroupBy = 'toggleGroupBy'
|
||||
export const useGroupBy = hooks => {
|
||||
hooks.getGroupByToggleProps = [defaultGetGroupByToggleProps]
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.flatColumnsDeps.push((deps, { instance }) => [
|
||||
hooks.visibleColumnsDeps.push((deps, { instance }) => [
|
||||
...deps,
|
||||
instance.state.groupBy,
|
||||
])
|
||||
hooks.flatColumns.push(flatColumns)
|
||||
hooks.visibleColumns.push(visibleColumns)
|
||||
hooks.useInstance.push(useInstance)
|
||||
hooks.prepareRow.push(prepareRow)
|
||||
}
|
||||
|
||||
useGroupBy.pluginName = 'useGroupBy'
|
||||
@ -62,12 +64,14 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
|
||||
if (action.type === actions.toggleGroupBy) {
|
||||
const { columnId, toggle } = action
|
||||
const { columnId, value: setGroupBy } = action
|
||||
|
||||
const resolvedToggle =
|
||||
typeof toggle !== 'undefined' ? toggle : !state.groupBy.includes(columnId)
|
||||
const resolvedGroupBy =
|
||||
typeof setGroupBy !== 'undefined'
|
||||
? setGroupBy
|
||||
: !state.groupBy.includes(columnId)
|
||||
|
||||
if (resolvedToggle) {
|
||||
if (resolvedGroupBy) {
|
||||
return {
|
||||
...state,
|
||||
groupBy: [...state.groupBy, columnId],
|
||||
@ -81,8 +85,8 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
}
|
||||
|
||||
function flatColumns(
|
||||
flatColumns,
|
||||
function visibleColumns(
|
||||
columns,
|
||||
{
|
||||
instance: {
|
||||
state: { groupBy },
|
||||
@ -93,18 +97,19 @@ function flatColumns(
|
||||
// before the headers are built
|
||||
|
||||
const groupByColumns = groupBy
|
||||
.map(g => flatColumns.find(col => col.id === g))
|
||||
.filter(col => !!col)
|
||||
const nonGroupByColumns = flatColumns.filter(col => !groupBy.includes(col.id))
|
||||
.map(g => columns.find(col => col.id === g))
|
||||
.filter(Boolean)
|
||||
|
||||
flatColumns = [...groupByColumns, ...nonGroupByColumns]
|
||||
const nonGroupByColumns = columns.filter(col => !groupBy.includes(col.id))
|
||||
|
||||
flatColumns.forEach(column => {
|
||||
columns = [...groupByColumns, ...nonGroupByColumns]
|
||||
|
||||
columns.forEach(column => {
|
||||
column.isGrouped = groupBy.includes(column.id)
|
||||
column.groupedIndex = groupBy.indexOf(column.id)
|
||||
})
|
||||
|
||||
return flatColumns
|
||||
return columns
|
||||
}
|
||||
|
||||
const defaultUserAggregations = {}
|
||||
@ -114,12 +119,11 @@ function useInstance(instance) {
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
flatHeaders,
|
||||
groupByFn = defaultGroupByFn,
|
||||
manualGroupBy,
|
||||
aggregations: userAggregations = defaultUserAggregations,
|
||||
hooks,
|
||||
plugins,
|
||||
state: { groupBy },
|
||||
dispatch,
|
||||
@ -127,13 +131,14 @@ function useInstance(instance) {
|
||||
manaulGroupBy,
|
||||
disableGroupBy,
|
||||
defaultCanGroupBy,
|
||||
getHooks,
|
||||
} = instance
|
||||
|
||||
ensurePluginOrder(plugins, [], 'useGroupBy', ['useSortBy', 'useExpanded'])
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
flatColumns.forEach(column => {
|
||||
allColumns.forEach(column => {
|
||||
const {
|
||||
accessor,
|
||||
defaultGroupBy: defaultColumnGroupBy,
|
||||
@ -142,11 +147,17 @@ function useInstance(instance) {
|
||||
|
||||
column.canGroupBy = accessor
|
||||
? getFirstDefined(
|
||||
column.canGroupBy,
|
||||
columnDisableGroupBy === true ? false : undefined,
|
||||
disableGroupBy === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: getFirstDefined(defaultColumnGroupBy, defaultCanGroupBy, false)
|
||||
: getFirstDefined(
|
||||
column.canGroupBy,
|
||||
defaultColumnGroupBy,
|
||||
defaultCanGroupBy,
|
||||
false
|
||||
)
|
||||
|
||||
if (column.canGroupBy) {
|
||||
column.toggleGroupBy = () => instance.toggleGroupBy(column.id)
|
||||
@ -155,34 +166,17 @@ function useInstance(instance) {
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
})
|
||||
|
||||
const toggleGroupBy = (columnId, toggle) => {
|
||||
dispatch({ type: actions.toggleGroupBy, columnId, toggle })
|
||||
const toggleGroupBy = (columnId, value) => {
|
||||
dispatch({ type: actions.toggleGroupBy, columnId, value })
|
||||
}
|
||||
|
||||
const getGroupByTogglePropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getGroupByToggleProps'
|
||||
)
|
||||
|
||||
flatHeaders.forEach(header => {
|
||||
header.getGroupByToggleProps = makePropGetter(
|
||||
getGroupByTogglePropsHooks(),
|
||||
getHooks().getGroupByToggleProps,
|
||||
{ instance: getInstance(), header }
|
||||
)
|
||||
})
|
||||
|
||||
hooks.prepareRow.push(row => {
|
||||
row.allCells.forEach(cell => {
|
||||
// Grouped cells are in the groupBy and the pivot cell for the row
|
||||
cell.isGrouped = cell.column.isGrouped && cell.column.id === row.groupByID
|
||||
// Repeated cells are any columns in the groupBy that are not grouped
|
||||
cell.isRepeatedValue = !cell.isGrouped && cell.column.isGrouped
|
||||
// Aggregated cells are not grouped, not repeated, but still have subRows
|
||||
cell.isAggregated =
|
||||
!cell.isGrouped && !cell.isRepeatedValue && row.canExpand
|
||||
})
|
||||
})
|
||||
|
||||
const [groupedRows, groupedFlatRows] = React.useMemo(() => {
|
||||
if (manualGroupBy || !groupBy.length) {
|
||||
return [rows, flatRows]
|
||||
@ -190,62 +184,75 @@ function useInstance(instance) {
|
||||
|
||||
// Ensure that the list of filtered columns exist
|
||||
const existingGroupBy = groupBy.filter(g =>
|
||||
flatColumns.find(col => col.id === g)
|
||||
allColumns.find(col => col.id === g)
|
||||
)
|
||||
|
||||
// Find the columns that can or are aggregating
|
||||
// Uses each column to aggregate rows into a single value
|
||||
const aggregateRowsToValues = (rows, isAggregated) => {
|
||||
const aggregateRowsToValues = (leafRows, groupedRows, depth) => {
|
||||
const values = {}
|
||||
|
||||
flatColumns.forEach(column => {
|
||||
allColumns.forEach(column => {
|
||||
// Don't aggregate columns that are in the groupBy
|
||||
if (existingGroupBy.includes(column.id)) {
|
||||
values[column.id] = rows[0] ? rows[0].values[column.id] : null
|
||||
values[column.id] = groupedRows[0]
|
||||
? groupedRows[0].values[column.id]
|
||||
: null
|
||||
return
|
||||
}
|
||||
|
||||
const columnValues = rows.map(d => d.values[column.id])
|
||||
// Get the columnValues to aggregate
|
||||
const groupedValues = groupedRows.map(row => row.values[column.id])
|
||||
|
||||
let aggregator = column.aggregate
|
||||
// Get the columnValues to aggregate
|
||||
const leafValues = leafRows.map(row => {
|
||||
let columnValue = row.values[column.id]
|
||||
|
||||
if (Array.isArray(aggregator)) {
|
||||
if (aggregator.length !== 2) {
|
||||
console.info({ column })
|
||||
throw new Error(
|
||||
`React Table: Complex aggregators must have 2 values, eg. aggregate: ['sum', 'count']. More info above...`
|
||||
)
|
||||
if (!depth && column.aggregatedValue) {
|
||||
const aggregateValueFn =
|
||||
typeof column.aggregateValue === 'function'
|
||||
? column.aggregateValue
|
||||
: userAggregations[column.aggregateValue] ||
|
||||
aggregations[column.aggregateValue]
|
||||
|
||||
if (!aggregateValueFn) {
|
||||
console.info({ column })
|
||||
throw new Error(
|
||||
`React Table: Invalid column.aggregateValue option for column listed above`
|
||||
)
|
||||
}
|
||||
|
||||
columnValue = aggregateValueFn(columnValue, row, column)
|
||||
}
|
||||
if (isAggregated) {
|
||||
aggregator = aggregator[1]
|
||||
} else {
|
||||
aggregator = aggregator[0]
|
||||
}
|
||||
}
|
||||
return columnValue
|
||||
})
|
||||
|
||||
// Aggregate the values
|
||||
let aggregateFn =
|
||||
typeof aggregator === 'function'
|
||||
? aggregator
|
||||
: userAggregations[aggregator] || aggregations[aggregator]
|
||||
typeof column.aggregate === 'function'
|
||||
? column.aggregate
|
||||
: userAggregations[column.aggregate] ||
|
||||
aggregations[column.aggregate]
|
||||
|
||||
if (aggregateFn) {
|
||||
values[column.id] = aggregateFn(columnValues, rows, isAggregated)
|
||||
} else if (aggregator) {
|
||||
values[column.id] = aggregateFn(leafValues, groupedValues)
|
||||
} else if (column.aggregate) {
|
||||
console.info({ column })
|
||||
throw new Error(
|
||||
`React Table: Invalid aggregate option for column listed above`
|
||||
`React Table: Invalid column.aggregate option for column listed above`
|
||||
)
|
||||
} else {
|
||||
values[column.id] = null
|
||||
}
|
||||
})
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
let groupedFlatRows = []
|
||||
|
||||
// Recursively group the data
|
||||
const groupRecursively = (rows, depth = 0, parentId) => {
|
||||
const groupUpRecursively = (rows, depth = 0, parentId) => {
|
||||
// This is the last level, just return the rows
|
||||
if (depth === existingGroupBy.length) {
|
||||
return rows
|
||||
@ -254,20 +261,23 @@ function useInstance(instance) {
|
||||
const columnId = existingGroupBy[depth]
|
||||
|
||||
// Group the rows together for this level
|
||||
let groupedRows = groupByFn(rows, columnId)
|
||||
let rowGroupsMap = groupByFn(rows, columnId)
|
||||
|
||||
// Recurse to sub rows before aggregation
|
||||
groupedRows = Object.entries(groupedRows).map(
|
||||
([groupByVal, subRows], index) => {
|
||||
// Peform aggregations for each group
|
||||
const aggregatedGroupedRows = Object.entries(rowGroupsMap).map(
|
||||
([groupByVal, groupedRows], index) => {
|
||||
let id = `${columnId}:${groupByVal}`
|
||||
id = parentId ? `${parentId}>${id}` : id
|
||||
|
||||
subRows = groupRecursively(subRows, depth + 1, id)
|
||||
// First, Recurse to group sub rows before aggregation
|
||||
const subRows = groupUpRecursively(groupedRows, depth + 1, id)
|
||||
|
||||
const values = aggregateRowsToValues(
|
||||
subRows,
|
||||
depth < existingGroupBy.length
|
||||
)
|
||||
// Flatten the leaf rows of the rows in this group
|
||||
const leafRows = depth
|
||||
? flattenBy(groupedRows, 'leafRows')
|
||||
: groupedRows
|
||||
|
||||
const values = aggregateRowsToValues(leafRows, groupedRows, depth)
|
||||
|
||||
const row = {
|
||||
id,
|
||||
@ -276,6 +286,7 @@ function useInstance(instance) {
|
||||
groupByVal,
|
||||
values,
|
||||
subRows,
|
||||
leafRows,
|
||||
depth,
|
||||
index,
|
||||
}
|
||||
@ -286,10 +297,10 @@ function useInstance(instance) {
|
||||
}
|
||||
)
|
||||
|
||||
return groupedRows
|
||||
return aggregatedGroupedRows
|
||||
}
|
||||
|
||||
const groupedRows = groupRecursively(rows)
|
||||
const groupedRows = groupUpRecursively(rows)
|
||||
|
||||
// Assign the new data
|
||||
return [groupedRows, groupedFlatRows]
|
||||
@ -298,7 +309,7 @@ function useInstance(instance) {
|
||||
groupBy,
|
||||
rows,
|
||||
flatRows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
userAggregations,
|
||||
groupByFn,
|
||||
])
|
||||
@ -321,3 +332,14 @@ function useInstance(instance) {
|
||||
toggleGroupBy,
|
||||
})
|
||||
}
|
||||
|
||||
function prepareRow(row) {
|
||||
row.allCells.forEach(cell => {
|
||||
// Grouped cells are in the groupBy and the pivot cell for the row
|
||||
cell.isGrouped = cell.column.isGrouped && cell.column.id === row.groupByID
|
||||
// Placeholder cells are any columns in the groupBy that are not grouped
|
||||
cell.isPlaceholder = !cell.isGrouped && cell.column.isGrouped
|
||||
// Aggregated cells are not grouped, not repeated, but still have subRows
|
||||
cell.isAggregated = !cell.isGrouped && !cell.isPlaceholder && row.canExpand
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@ import React from 'react'
|
||||
import {
|
||||
actions,
|
||||
ensurePluginOrder,
|
||||
expandRows,
|
||||
functionalUpdate,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
import { expandRows } from '../utils'
|
||||
|
||||
const pluginName = 'usePagination'
|
||||
|
||||
|
||||
297
src/plugin-hooks/usePivotColumns.js
Normal file
297
src/plugin-hooks/usePivotColumns.js
Normal file
@ -0,0 +1,297 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import {
|
||||
actions,
|
||||
makePropGetter,
|
||||
ensurePluginOrder,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
} from '../publicUtils'
|
||||
|
||||
import { flattenColumns, getFirstDefined } from '../utils'
|
||||
|
||||
// Actions
|
||||
actions.resetPivot = 'resetPivot'
|
||||
actions.togglePivot = 'togglePivot'
|
||||
|
||||
export const usePivotColumns = hooks => {
|
||||
hooks.getPivotToggleProps = [defaultGetPivotToggleProps]
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.useInstanceAfterData.push(useInstanceAfterData)
|
||||
hooks.allColumns.push(allColumns)
|
||||
hooks.accessValue.push(accessValue)
|
||||
hooks.materializedColumns.push(materializedColumns)
|
||||
hooks.materializedColumnsDeps.push(materializedColumnsDeps)
|
||||
hooks.visibleColumns.push(visibleColumns)
|
||||
hooks.visibleColumnsDeps.push(visibleColumnsDeps)
|
||||
hooks.useInstance.push(useInstance)
|
||||
hooks.prepareRow.push(prepareRow)
|
||||
}
|
||||
|
||||
usePivotColumns.pluginName = 'usePivotColumns'
|
||||
|
||||
const defaultPivotColumns = []
|
||||
|
||||
const defaultGetPivotToggleProps = (props, { header }) => [
|
||||
props,
|
||||
{
|
||||
onClick: header.canPivot
|
||||
? e => {
|
||||
e.persist()
|
||||
header.togglePivot()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: header.canPivot ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle Pivot',
|
||||
},
|
||||
]
|
||||
|
||||
// Reducer
|
||||
function reducer(state, action, previousState, instance) {
|
||||
if (action.type === actions.init) {
|
||||
return {
|
||||
pivotColumns: defaultPivotColumns,
|
||||
...state,
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === actions.resetPivot) {
|
||||
return {
|
||||
...state,
|
||||
pivotColumns: instance.initialState.pivotColumns || defaultPivotColumns,
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === actions.togglePivot) {
|
||||
const { columnId, value: setPivot } = action
|
||||
|
||||
const resolvedPivot =
|
||||
typeof setPivot !== 'undefined'
|
||||
? setPivot
|
||||
: !state.pivotColumns.includes(columnId)
|
||||
|
||||
if (resolvedPivot) {
|
||||
return {
|
||||
...state,
|
||||
pivotColumns: [...state.pivotColumns, columnId],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
pivotColumns: state.pivotColumns.filter(d => d !== columnId),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useInstanceAfterData(instance) {
|
||||
instance.allColumns.forEach(column => {
|
||||
column.isPivotSource = instance.state.pivotColumns.includes(column.id)
|
||||
})
|
||||
}
|
||||
|
||||
function allColumns(columns, { instance }) {
|
||||
columns.forEach(column => {
|
||||
column.isPivotSource = instance.state.pivotColumns.includes(column.id)
|
||||
column.uniqueValues = new Set()
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
function accessValue(value, { column }) {
|
||||
if (column.uniqueValues && typeof value !== 'undefined') {
|
||||
column.uniqueValues.add(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function materializedColumns(materialized, { instance }) {
|
||||
const { allColumns, state } = instance
|
||||
|
||||
if (!state.pivotColumns.length || !state.groupBy || !state.groupBy.length) {
|
||||
return materialized
|
||||
}
|
||||
|
||||
const pivotColumns = state.pivotColumns
|
||||
.map(id => allColumns.find(d => d.id === id))
|
||||
.filter(Boolean)
|
||||
|
||||
const sourceColumns = allColumns.filter(
|
||||
d =>
|
||||
!d.isPivotSource &&
|
||||
!state.groupBy.includes(d.id) &&
|
||||
!state.pivotColumns.includes(d.id)
|
||||
)
|
||||
|
||||
const buildPivotColumns = (depth = 0, parent, pivotFilters = []) => {
|
||||
const pivotColumn = pivotColumns[depth]
|
||||
|
||||
if (!pivotColumn) {
|
||||
return sourceColumns.map(sourceColumn => {
|
||||
// TODO: We could offer support here for renesting pivoted
|
||||
// columns inside copies of their header groups. For now,
|
||||
// that seems like it would be (1) overkill on nesting, considering
|
||||
// you already get nesting for every pivot level and (2)
|
||||
// really hard. :)
|
||||
|
||||
return {
|
||||
...sourceColumn,
|
||||
canPivot: false,
|
||||
isPivoted: true,
|
||||
parent,
|
||||
depth: depth,
|
||||
id: `${parent ? `${parent.id}.${sourceColumn.id}` : sourceColumn.id}`,
|
||||
accessor: (originalRow, i, row) => {
|
||||
if (pivotFilters.every(filter => filter(row))) {
|
||||
return row.values[sourceColumn.id]
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const uniqueValues = Array.from(pivotColumn.uniqueValues).sort()
|
||||
|
||||
return uniqueValues.map(uniqueValue => {
|
||||
const columnGroup = {
|
||||
...pivotColumn,
|
||||
Header:
|
||||
pivotColumn.PivotHeader || typeof pivotColumn.header === 'string'
|
||||
? `${pivotColumn.Header}: ${uniqueValue}`
|
||||
: uniqueValue,
|
||||
isPivotGroup: true,
|
||||
parent,
|
||||
depth,
|
||||
id: parent
|
||||
? `${parent.id}.${pivotColumn.id}.${uniqueValue}`
|
||||
: `${pivotColumn.id}.${uniqueValue}`,
|
||||
pivotValue: uniqueValue,
|
||||
}
|
||||
|
||||
columnGroup.columns = buildPivotColumns(depth + 1, columnGroup, [
|
||||
...pivotFilters,
|
||||
row => row.values[pivotColumn.id] === uniqueValue,
|
||||
])
|
||||
|
||||
return columnGroup
|
||||
})
|
||||
}
|
||||
|
||||
const newMaterialized = flattenColumns(buildPivotColumns())
|
||||
|
||||
return [...materialized, ...newMaterialized]
|
||||
}
|
||||
|
||||
function materializedColumnsDeps(
|
||||
deps,
|
||||
{
|
||||
instance: {
|
||||
state: { pivotColumns, groupBy },
|
||||
},
|
||||
}
|
||||
) {
|
||||
return [...deps, pivotColumns, groupBy]
|
||||
}
|
||||
|
||||
function visibleColumns(visibleColumns, { instance: { state } }) {
|
||||
visibleColumns = visibleColumns.filter(d => !d.isPivotSource)
|
||||
|
||||
if (state.pivotColumns.length && state.groupBy && state.groupBy.length) {
|
||||
visibleColumns = visibleColumns.filter(
|
||||
column => column.isGrouped || column.isPivoted
|
||||
)
|
||||
}
|
||||
|
||||
return visibleColumns
|
||||
}
|
||||
|
||||
function visibleColumnsDeps(deps, { instance }) {
|
||||
return [...deps, instance.state.pivotColumns, instance.state.groupBy]
|
||||
}
|
||||
|
||||
function useInstance(instance) {
|
||||
const {
|
||||
columns,
|
||||
allColumns,
|
||||
flatHeaders,
|
||||
// pivotFn = defaultPivotFn,
|
||||
// manualPivot,
|
||||
getHooks,
|
||||
plugins,
|
||||
dispatch,
|
||||
autoResetPivot = true,
|
||||
manaulPivot,
|
||||
disablePivot,
|
||||
defaultCanPivot,
|
||||
} = instance
|
||||
|
||||
ensurePluginOrder(plugins, ['useGroupBy'], 'usePivotColumns', [
|
||||
'useSortBy',
|
||||
'useExpanded',
|
||||
])
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
allColumns.forEach(column => {
|
||||
const {
|
||||
accessor,
|
||||
defaultPivot: defaultColumnPivot,
|
||||
disablePivot: columnDisablePivot,
|
||||
} = column
|
||||
|
||||
column.canPivot = accessor
|
||||
? getFirstDefined(
|
||||
column.canPivot,
|
||||
columnDisablePivot === true ? false : undefined,
|
||||
disablePivot === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: getFirstDefined(
|
||||
column.canPivot,
|
||||
defaultColumnPivot,
|
||||
defaultCanPivot,
|
||||
false
|
||||
)
|
||||
|
||||
if (column.canPivot) {
|
||||
column.togglePivot = () => instance.togglePivot(column.id)
|
||||
}
|
||||
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
})
|
||||
|
||||
const togglePivot = (columnId, value) => {
|
||||
dispatch({ type: actions.togglePivot, columnId, value })
|
||||
}
|
||||
|
||||
flatHeaders.forEach(header => {
|
||||
header.getPivotToggleProps = makePropGetter(
|
||||
getHooks().getPivotToggleProps,
|
||||
{
|
||||
instance: getInstance(),
|
||||
header,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const getAutoResetPivot = useGetLatest(autoResetPivot)
|
||||
|
||||
useMountedLayoutEffect(() => {
|
||||
if (getAutoResetPivot()) {
|
||||
dispatch({ type: actions.resetPivot })
|
||||
}
|
||||
}, [dispatch, manaulPivot ? null : columns])
|
||||
|
||||
Object.assign(instance, {
|
||||
togglePivot,
|
||||
})
|
||||
}
|
||||
|
||||
function prepareRow(row) {
|
||||
row.allCells.forEach(cell => {
|
||||
// Grouped cells are in the pivotColumns and the pivot cell for the row
|
||||
cell.isPivoted = cell.column.isPivoted
|
||||
})
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import {
|
||||
actions,
|
||||
defaultColumn,
|
||||
getFirstDefined,
|
||||
makePropGetter,
|
||||
useGetLatest,
|
||||
} from '../utils'
|
||||
import { useConsumeHookGetter } from '../publicUtils'
|
||||
} from '../publicUtils'
|
||||
|
||||
import { getFirstDefined } from '../utils'
|
||||
|
||||
// Default Column
|
||||
defaultColumn.canResize = true
|
||||
@ -118,6 +118,7 @@ const defaultGetResizerProps = (props, { instance, header }) => {
|
||||
cursor: 'ew-resize',
|
||||
},
|
||||
draggable: false,
|
||||
role: 'separator',
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -193,16 +194,12 @@ const useInstanceBeforeDimensions = instance => {
|
||||
const {
|
||||
flatHeaders,
|
||||
disableResizing,
|
||||
getHooks,
|
||||
state: { columnResizing },
|
||||
} = instance
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
const getResizerPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getResizerProps'
|
||||
)
|
||||
|
||||
flatHeaders.forEach(header => {
|
||||
const canResize = getFirstDefined(
|
||||
header.disableResizing === true ? false : undefined,
|
||||
@ -215,7 +212,7 @@ const useInstanceBeforeDimensions = instance => {
|
||||
header.isResizing = columnResizing.isResizingColumn === header.id
|
||||
|
||||
if (canResize) {
|
||||
header.getResizerProps = makePropGetter(getResizerPropsHooks(), {
|
||||
header.getResizerProps = makePropGetter(getHooks().getResizerProps, {
|
||||
instance: getInstance(),
|
||||
header,
|
||||
})
|
||||
|
||||
@ -6,8 +6,7 @@ import {
|
||||
ensurePluginOrder,
|
||||
useGetLatest,
|
||||
useMountedLayoutEffect,
|
||||
useConsumeHookGetter,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
const pluginName = 'useRowSelect'
|
||||
|
||||
@ -20,8 +19,8 @@ export const useRowSelect = hooks => {
|
||||
hooks.getToggleRowSelectedProps = [defaultGetToggleRowSelectedProps]
|
||||
hooks.getToggleAllRowsSelectedProps = [defaultGetToggleAllRowsSelectedProps]
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.useRows.push(useRows)
|
||||
hooks.useInstance.push(useInstance)
|
||||
hooks.prepareRow.push(prepareRow)
|
||||
}
|
||||
|
||||
useRowSelect.pluginName = pluginName
|
||||
@ -86,11 +85,11 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
|
||||
if (action.type === actions.toggleAllRowsSelected) {
|
||||
const { selected } = action
|
||||
const { value: setSelected } = action
|
||||
const { isAllRowsSelected, flatRowsById } = instance
|
||||
|
||||
const selectAll =
|
||||
typeof selected !== 'undefined' ? selected : !isAllRowsSelected
|
||||
typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected
|
||||
|
||||
if (selectAll) {
|
||||
const selectedRowIds = {}
|
||||
@ -112,7 +111,7 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
|
||||
if (action.type === actions.toggleRowSelected) {
|
||||
const { id, selected } = action
|
||||
const { id, value: setSelected } = action
|
||||
const { flatGroupedRowsById } = instance
|
||||
|
||||
// Join the ids of deep rows
|
||||
@ -120,7 +119,8 @@ function reducer(state, action, previousState, instance) {
|
||||
// in a flat object
|
||||
const row = flatGroupedRowsById[id]
|
||||
const isSelected = row.isSelected
|
||||
const shouldExist = typeof selected !== 'undefined' ? selected : !isSelected
|
||||
const shouldExist =
|
||||
typeof setSelected !== 'undefined' ? setSelected : !isSelected
|
||||
|
||||
if (isSelected === shouldExist) {
|
||||
return state
|
||||
@ -153,34 +153,11 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
}
|
||||
|
||||
function useRows(rows, { instance }) {
|
||||
const {
|
||||
state: { selectedRowIds },
|
||||
} = instance
|
||||
|
||||
instance.selectedFlatRows = React.useMemo(() => {
|
||||
const selectedFlatRows = []
|
||||
|
||||
rows.forEach(row => {
|
||||
const isSelected = getRowIsSelected(row, selectedRowIds)
|
||||
row.isSelected = !!isSelected
|
||||
row.isSomeSelected = isSelected === null
|
||||
|
||||
if (isSelected) {
|
||||
selectedFlatRows.push(row)
|
||||
}
|
||||
})
|
||||
|
||||
return selectedFlatRows
|
||||
}, [rows, selectedRowIds])
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
function useInstance(instance) {
|
||||
const {
|
||||
data,
|
||||
hooks,
|
||||
rows,
|
||||
getHooks,
|
||||
plugins,
|
||||
flatRows,
|
||||
autoResetSelectedRows = true,
|
||||
@ -209,6 +186,22 @@ function useInstance(instance) {
|
||||
return [all, grouped]
|
||||
}, [flatRows])
|
||||
|
||||
const selectedFlatRows = React.useMemo(() => {
|
||||
const selectedFlatRows = []
|
||||
|
||||
rows.forEach(row => {
|
||||
const isSelected = getRowIsSelected(row, selectedRowIds)
|
||||
row.isSelected = !!isSelected
|
||||
row.isSomeSelected = isSelected === null
|
||||
|
||||
if (isSelected) {
|
||||
selectedFlatRows.push(row)
|
||||
}
|
||||
})
|
||||
|
||||
return selectedFlatRows
|
||||
}, [rows, selectedRowIds])
|
||||
|
||||
let isAllRowsSelected = Boolean(
|
||||
Object.keys(flatRowsById).length && Object.keys(selectedRowIds).length
|
||||
)
|
||||
@ -227,41 +220,23 @@ function useInstance(instance) {
|
||||
}
|
||||
}, [dispatch, data])
|
||||
|
||||
const toggleAllRowsSelected = selected =>
|
||||
dispatch({ type: actions.toggleAllRowsSelected, selected })
|
||||
const toggleAllRowsSelected = value =>
|
||||
dispatch({ type: actions.toggleAllRowsSelected, value })
|
||||
|
||||
const toggleRowSelected = (id, selected) =>
|
||||
dispatch({ type: actions.toggleRowSelected, id, selected })
|
||||
const toggleRowSelected = (id, value) =>
|
||||
dispatch({ type: actions.toggleRowSelected, id, value })
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
const getToggleAllRowsSelectedPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getToggleAllRowsSelectedProps'
|
||||
)
|
||||
|
||||
const getToggleAllRowsSelectedProps = makePropGetter(
|
||||
getToggleAllRowsSelectedPropsHooks(),
|
||||
getHooks().getToggleAllRowsSelectedProps,
|
||||
{ instance: getInstance() }
|
||||
)
|
||||
|
||||
const getToggleRowSelectedPropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getToggleRowSelectedProps'
|
||||
)
|
||||
|
||||
hooks.prepareRow.push(row => {
|
||||
row.toggleRowSelected = set => toggleRowSelected(row.id, set)
|
||||
|
||||
row.getToggleRowSelectedProps = makePropGetter(
|
||||
getToggleRowSelectedPropsHooks(),
|
||||
{ instance: getInstance(), row }
|
||||
)
|
||||
})
|
||||
|
||||
Object.assign(instance, {
|
||||
flatRowsById,
|
||||
flatGroupedRowsById,
|
||||
selectedFlatRows,
|
||||
toggleRowSelected,
|
||||
toggleAllRowsSelected,
|
||||
getToggleAllRowsSelectedProps,
|
||||
@ -269,6 +244,15 @@ function useInstance(instance) {
|
||||
})
|
||||
}
|
||||
|
||||
function prepareRow(row, { instance }) {
|
||||
row.toggleRowSelected = set => instance.toggleRowSelected(row.id, set)
|
||||
|
||||
row.getToggleRowSelectedProps = makePropGetter(
|
||||
instance.getHooks().getToggleRowSelectedProps,
|
||||
{ instance: instance, row }
|
||||
)
|
||||
}
|
||||
|
||||
function getRowIsSelected(row, selectedRowIds) {
|
||||
if (selectedRowIds[row.id]) {
|
||||
return true
|
||||
|
||||
@ -5,20 +5,31 @@ import {
|
||||
functionalUpdate,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
const defaultInitialRowStateAccessor = originalRow => ({})
|
||||
const defaultInitialCellStateAccessor = originalRow => ({})
|
||||
|
||||
// Actions
|
||||
actions.setRowState = 'setRowState'
|
||||
actions.setCellState = 'setCellState'
|
||||
actions.resetRowState = 'resetRowState'
|
||||
|
||||
export const useRowState = hooks => {
|
||||
hooks.stateReducers.push(reducer)
|
||||
hooks.useInstance.push(useInstance)
|
||||
hooks.prepareRow.push(prepareRow)
|
||||
}
|
||||
|
||||
useRowState.pluginName = 'useRowState'
|
||||
|
||||
function reducer(state, action, previousState, instance) {
|
||||
const {
|
||||
initialRowStateAccessor = defaultInitialRowStateAccessor,
|
||||
initialCellStateAccessor = defaultInitialCellStateAccessor,
|
||||
rowsById,
|
||||
} = instance
|
||||
|
||||
if (action.type === actions.init) {
|
||||
return {
|
||||
rowState: {},
|
||||
@ -34,82 +45,75 @@ function reducer(state, action, previousState, instance) {
|
||||
}
|
||||
|
||||
if (action.type === actions.setRowState) {
|
||||
const { id, value } = action
|
||||
const { rowId, value } = action
|
||||
|
||||
const oldRowState =
|
||||
typeof state.rowState[rowId] !== 'undefined'
|
||||
? state.rowState[rowId]
|
||||
: initialRowStateAccessor(rowsById[rowId].original)
|
||||
|
||||
return {
|
||||
...state,
|
||||
rowState: {
|
||||
...state.rowState,
|
||||
[id]: functionalUpdate(value, state.rowState[id] || {}),
|
||||
[rowId]: functionalUpdate(value, oldRowState),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === actions.setCellState) {
|
||||
const { rowId, columnId, value } = action
|
||||
|
||||
const oldRowState =
|
||||
typeof state.rowState[rowId] !== 'undefined'
|
||||
? state.rowState[rowId]
|
||||
: initialRowStateAccessor(rowsById[rowId].original)
|
||||
|
||||
const oldCellState =
|
||||
typeof oldRowState?.cellState?.[columnId] !== 'undefined'
|
||||
? oldRowState.cellState[columnId]
|
||||
: initialCellStateAccessor(rowsById[rowId].original)
|
||||
|
||||
return {
|
||||
...state,
|
||||
rowState: {
|
||||
...state.rowState,
|
||||
[rowId]: {
|
||||
...oldRowState,
|
||||
cellState: {
|
||||
...(oldRowState.cellState || {}),
|
||||
[columnId]: functionalUpdate(value, oldCellState),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useInstance(instance) {
|
||||
const {
|
||||
hooks,
|
||||
initialRowStateAccessor,
|
||||
autoResetRowState = true,
|
||||
state: { rowState },
|
||||
data,
|
||||
dispatch,
|
||||
} = instance
|
||||
const { autoResetRowState = true, data, dispatch } = instance
|
||||
|
||||
const setRowState = React.useCallback(
|
||||
(id, value, columnId) =>
|
||||
(rowId, value) =>
|
||||
dispatch({
|
||||
type: actions.setRowState,
|
||||
id,
|
||||
rowId,
|
||||
value,
|
||||
columnId,
|
||||
}),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setCellState = React.useCallback(
|
||||
(rowPath, columnId, value) => {
|
||||
return setRowState(
|
||||
rowPath,
|
||||
old => {
|
||||
return {
|
||||
...old,
|
||||
cellState: {
|
||||
...old.cellState,
|
||||
[columnId]: functionalUpdate(
|
||||
value,
|
||||
(old.cellState || {})[columnId] || {}
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
columnId
|
||||
)
|
||||
},
|
||||
[setRowState]
|
||||
(rowId, columnId, value) =>
|
||||
dispatch({
|
||||
type: actions.setCellState,
|
||||
rowId,
|
||||
columnId,
|
||||
value,
|
||||
}),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
hooks.prepareRow.push(row => {
|
||||
if (row.original) {
|
||||
row.state =
|
||||
(typeof rowState[row.id] !== 'undefined'
|
||||
? rowState[row.id]
|
||||
: initialRowStateAccessor && initialRowStateAccessor(row)) || {}
|
||||
|
||||
row.setState = updater => {
|
||||
return setRowState(row.id, updater)
|
||||
}
|
||||
|
||||
row.cells.forEach(cell => {
|
||||
cell.state = row.state.cellState || {}
|
||||
|
||||
cell.setState = updater => {
|
||||
return setCellState(row.id, cell.column.id, updater)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getAutoResetRowState = useGetLatest(autoResetRowState)
|
||||
|
||||
useMountedLayoutEffect(() => {
|
||||
@ -123,3 +127,37 @@ function useInstance(instance) {
|
||||
setCellState,
|
||||
})
|
||||
}
|
||||
|
||||
function prepareRow(row, { instance }) {
|
||||
const {
|
||||
initialRowStateAccessor = defaultInitialRowStateAccessor,
|
||||
initialCellStateAccessor = defaultInitialCellStateAccessor,
|
||||
state: { rowState },
|
||||
} = instance
|
||||
|
||||
if (row.original) {
|
||||
row.state =
|
||||
typeof rowState[row.id] !== 'undefined'
|
||||
? rowState[row.id]
|
||||
: initialRowStateAccessor(row.original)
|
||||
|
||||
row.setState = updater => {
|
||||
return instance.setRowState(row.id, updater)
|
||||
}
|
||||
|
||||
row.cells.forEach(cell => {
|
||||
if (!row.state.cellState) {
|
||||
row.state.cellState = {}
|
||||
}
|
||||
|
||||
cell.state =
|
||||
typeof row.state.cellState[cell.column.id] !== 'undefined'
|
||||
? row.state.cellState[cell.column.id]
|
||||
: initialCellStateAccessor(row.original)
|
||||
|
||||
cell.setState = updater => {
|
||||
return instance.setCellState(row.id, cell.column.id, updater)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,12 @@ import {
|
||||
ensurePluginOrder,
|
||||
defaultColumn,
|
||||
makePropGetter,
|
||||
useConsumeHookGetter,
|
||||
getFirstDefined,
|
||||
defaultOrderByFn,
|
||||
isFunction,
|
||||
useGetLatest,
|
||||
useMountedLayoutEffect,
|
||||
} from '../utils'
|
||||
} from '../publicUtils'
|
||||
|
||||
import { getFirstDefined, isFunction } from '../utils'
|
||||
|
||||
import * as sortTypes from '../sortTypes'
|
||||
|
||||
@ -84,7 +83,7 @@ function reducer(state, action, previousState, instance) {
|
||||
const { columnId, desc, multi } = action
|
||||
|
||||
const {
|
||||
flatColumns,
|
||||
allColumns,
|
||||
disableMultiSort,
|
||||
disableSortRemove,
|
||||
disableMultiRemove,
|
||||
@ -94,7 +93,7 @@ function reducer(state, action, previousState, instance) {
|
||||
const { sortBy } = state
|
||||
|
||||
// Find the column for this columnId
|
||||
const column = flatColumns.find(d => d.id === columnId)
|
||||
const column = allColumns.find(d => d.id === columnId)
|
||||
const { sortDescFirst } = column
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
@ -181,7 +180,7 @@ function useInstance(instance) {
|
||||
const {
|
||||
data,
|
||||
rows,
|
||||
flatColumns,
|
||||
allColumns,
|
||||
orderByFn = defaultOrderByFn,
|
||||
sortTypes: userSortTypes,
|
||||
manualSortBy,
|
||||
@ -191,6 +190,7 @@ function useInstance(instance) {
|
||||
state: { sortBy },
|
||||
dispatch,
|
||||
plugins,
|
||||
getHooks,
|
||||
autoResetSortBy = true,
|
||||
} = instance
|
||||
|
||||
@ -204,11 +204,6 @@ function useInstance(instance) {
|
||||
// use reference to avoid memory leak in #1608
|
||||
const getInstance = useGetLatest(instance)
|
||||
|
||||
const getSortByTogglePropsHooks = useConsumeHookGetter(
|
||||
getInstance().hooks,
|
||||
'getSortByToggleProps'
|
||||
)
|
||||
|
||||
// Add the getSortByToggleProps method to columns and headers
|
||||
flatHeaders.forEach(column => {
|
||||
const {
|
||||
@ -237,10 +232,13 @@ function useInstance(instance) {
|
||||
}
|
||||
}
|
||||
|
||||
column.getSortByToggleProps = makePropGetter(getSortByTogglePropsHooks(), {
|
||||
instance: getInstance(),
|
||||
column,
|
||||
})
|
||||
column.getSortByToggleProps = makePropGetter(
|
||||
getHooks().getSortByToggleProps,
|
||||
{
|
||||
instance: getInstance(),
|
||||
column,
|
||||
}
|
||||
)
|
||||
|
||||
const columnSort = sortBy.find(d => d.id === id)
|
||||
column.isSorted = !!columnSort
|
||||
@ -255,7 +253,7 @@ function useInstance(instance) {
|
||||
|
||||
// Filter out sortBys that correspond to non existing columns
|
||||
const availableSortBy = sortBy.filter(sort =>
|
||||
flatColumns.find(col => col.id === sort.id)
|
||||
allColumns.find(col => col.id === sort.id)
|
||||
)
|
||||
|
||||
const sortData = rows => {
|
||||
@ -266,7 +264,7 @@ function useInstance(instance) {
|
||||
rows,
|
||||
availableSortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
const column = allColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(
|
||||
@ -301,7 +299,7 @@ function useInstance(instance) {
|
||||
// Map the directions
|
||||
availableSortBy.map(sort => {
|
||||
// Detect and use the sortInverted option
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
const column = allColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (column && column.sortInverted) {
|
||||
return sort.desc
|
||||
@ -323,7 +321,7 @@ function useInstance(instance) {
|
||||
}
|
||||
|
||||
return sortData(rows)
|
||||
}, [manualSortBy, sortBy, rows, flatColumns, orderByFn, userSortTypes])
|
||||
}, [manualSortBy, sortBy, rows, allColumns, orderByFn, userSortTypes])
|
||||
|
||||
const getAutoResetSortBy = useGetLatest(autoResetSortBy)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
let renderErr = 'Renderer Error'
|
||||
let renderErr = 'Renderer Error ☝️'
|
||||
|
||||
export const actions = {
|
||||
init: 'init',
|
||||
@ -94,11 +94,11 @@ export const makePropGetter = (hooks, meta = {}) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const reduceHooks = (hooks, initial, meta = {}) =>
|
||||
export const reduceHooks = (hooks, initial, meta = {}, allowUndefined) =>
|
||||
hooks.reduce((prev, next) => {
|
||||
const nextValue = next(prev, meta)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof nextValue === 'undefined') {
|
||||
if (!allowUndefined && typeof nextValue === 'undefined') {
|
||||
console.info(next)
|
||||
throw new Error(
|
||||
'React Table: A reducer hook ☝️ just returned undefined! This is not allowed.'
|
||||
@ -108,9 +108,9 @@ export const reduceHooks = (hooks, initial, meta = {}) =>
|
||||
return nextValue
|
||||
}, initial)
|
||||
|
||||
export const loopHooks = (hooks, meta = {}) =>
|
||||
export const loopHooks = (hooks, context, meta = {}) =>
|
||||
hooks.forEach(hook => {
|
||||
const nextValue = hook(meta)
|
||||
const nextValue = hook(context, meta)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof nextValue !== 'undefined') {
|
||||
console.info(hook, nextValue)
|
||||
@ -190,14 +190,12 @@ export function useMountedLayoutEffect(fn, deps) {
|
||||
|
||||
export function useAsyncDebounce(defaultFn, defaultWait = 0) {
|
||||
const debounceRef = React.useRef({})
|
||||
debounceRef.current.defaultFn = defaultFn
|
||||
debounceRef.current.defaultWait = defaultWait
|
||||
|
||||
const debounce = React.useCallback(
|
||||
async (
|
||||
fn = debounceRef.current.defaultFn,
|
||||
wait = debounceRef.current.defaultWait
|
||||
) => {
|
||||
const getDefaultFn = useGetLatest(defaultFn)
|
||||
const getDefaultWait = useGetLatest(defaultWait)
|
||||
|
||||
return React.useCallback(
|
||||
async (...args) => {
|
||||
if (!debounceRef.current.promise) {
|
||||
debounceRef.current.promise = new Promise((resolve, reject) => {
|
||||
debounceRef.current.resolve = resolve
|
||||
@ -212,26 +210,18 @@ export function useAsyncDebounce(defaultFn, defaultWait = 0) {
|
||||
debounceRef.current.timeout = setTimeout(async () => {
|
||||
delete debounceRef.current.timeout
|
||||
try {
|
||||
debounceRef.current.resolve(await fn())
|
||||
debounceRef.current.resolve(await getDefaultFn()(...args))
|
||||
} catch (err) {
|
||||
debounceRef.current.reject(err)
|
||||
} finally {
|
||||
delete debounceRef.current.promise
|
||||
}
|
||||
}, wait)
|
||||
}, getDefaultWait())
|
||||
|
||||
return debounceRef.current.promise
|
||||
},
|
||||
[]
|
||||
[getDefaultFn, getDefaultWait]
|
||||
)
|
||||
|
||||
return debounce
|
||||
}
|
||||
|
||||
export function useConsumeHookGetter(hooks, hookName) {
|
||||
const getter = useGetLatest(hooks[hookName])
|
||||
hooks[hookName] = undefined
|
||||
return getter
|
||||
}
|
||||
|
||||
export function makeRenderer(instance, column, meta = {}) {
|
||||
@ -239,6 +229,7 @@ export function makeRenderer(instance, column, meta = {}) {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
console.info(column)
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import React from 'react'
|
||||
|
||||
// Token pagination behaves a bit different from
|
||||
|
||||
293
src/utils.js
293
src/utils.js
@ -1,7 +1,5 @@
|
||||
import React from 'react'
|
||||
import { defaultColumn } from './publicUtils'
|
||||
|
||||
export * from './publicUtils'
|
||||
import { defaultColumn, reduceHooks } from './publicUtils'
|
||||
|
||||
// Find the depth of the columns
|
||||
export function findMaxDepth(columns, depth = 0) {
|
||||
@ -13,10 +11,29 @@ export function findMaxDepth(columns, depth = 0) {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function decorateColumn(column, userDefaultColumn, parent, depth, index) {
|
||||
// Apply the userDefaultColumn
|
||||
column = { ...defaultColumn, ...userDefaultColumn, ...column }
|
||||
// Build the visible columns, headers and flat column list
|
||||
export function linkColumnStructure(columns, parent, depth = 0) {
|
||||
return columns.map(column => {
|
||||
column = {
|
||||
...column,
|
||||
parent,
|
||||
depth,
|
||||
}
|
||||
|
||||
assignColumnAccessor(column)
|
||||
|
||||
if (column.columns) {
|
||||
column.columns = linkColumnStructure(column.columns, column, depth + 1)
|
||||
}
|
||||
return column
|
||||
})
|
||||
}
|
||||
|
||||
export function flattenColumns(columns) {
|
||||
return flattenBy(columns, 'columns')
|
||||
}
|
||||
|
||||
export function assignColumnAccessor(column) {
|
||||
// First check for string accessor
|
||||
let { id, accessor, Header } = column
|
||||
|
||||
@ -40,123 +57,199 @@ function decorateColumn(column, userDefaultColumn, parent, depth, index) {
|
||||
throw new Error('A column ID (or string accessor) is required!')
|
||||
}
|
||||
|
||||
column = {
|
||||
// Make sure there is a fallback header, just in case
|
||||
Header: () => <> </>,
|
||||
Footer: () => <> </>,
|
||||
...column,
|
||||
// Materialize and override this stuff
|
||||
Object.assign(column, {
|
||||
id,
|
||||
accessor,
|
||||
parent,
|
||||
depth,
|
||||
index,
|
||||
}
|
||||
})
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
export function decorateColumnTree(columns, defaultColumn, parent, depth = 0) {
|
||||
return columns.map((column, columnIndex) => {
|
||||
column = decorateColumn(column, defaultColumn, parent, depth, columnIndex)
|
||||
if (column.columns) {
|
||||
column.columns = decorateColumnTree(
|
||||
column.columns,
|
||||
defaultColumn,
|
||||
column,
|
||||
depth + 1
|
||||
)
|
||||
}
|
||||
return column
|
||||
// Find the depth of the columns
|
||||
export function dedupeBy(arr, fn) {
|
||||
return [...arr]
|
||||
.reverse()
|
||||
.filter((d, i, all) => all.findIndex(dd => fn(dd) === fn(d)) === i)
|
||||
.reverse()
|
||||
}
|
||||
|
||||
export function decorateColumn(column, userDefaultColumn) {
|
||||
if (!userDefaultColumn) {
|
||||
throw new Error()
|
||||
}
|
||||
Object.assign(column, {
|
||||
// Make sure there is a fallback header, just in case
|
||||
Header: () => <> </>,
|
||||
Footer: () => <> </>,
|
||||
...defaultColumn,
|
||||
...userDefaultColumn,
|
||||
...column,
|
||||
})
|
||||
return column
|
||||
}
|
||||
|
||||
export function accessRowsForColumn({
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
column,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
accessValueHooks,
|
||||
getInstance,
|
||||
}) {
|
||||
// Access the row's data column-by-column
|
||||
// We do it this way so we can incrementally add materialized
|
||||
// columns after the first pass and avoid excessive looping
|
||||
const accessRow = (originalRow, rowIndex, depth = 0, parent, parentRows) => {
|
||||
// Keep the original reference around
|
||||
const original = originalRow
|
||||
|
||||
const id = getRowId(originalRow, rowIndex, parent)
|
||||
|
||||
let row = rowsById[id]
|
||||
|
||||
// If the row hasn't been created, let's make it
|
||||
if (!row) {
|
||||
row = {
|
||||
id,
|
||||
original,
|
||||
index: rowIndex,
|
||||
depth,
|
||||
cells: [{}], // This is a dummy cell
|
||||
}
|
||||
|
||||
// Override common array functions (and the dummy cell's getCellProps function)
|
||||
// to show an error if it is accessed without calling prepareRow
|
||||
row.cells.map = unpreparedAccessWarning
|
||||
row.cells.filter = unpreparedAccessWarning
|
||||
row.cells.forEach = unpreparedAccessWarning
|
||||
row.cells[0].getCellProps = unpreparedAccessWarning
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
|
||||
// Push this row into the parentRows array
|
||||
parentRows.push(row)
|
||||
// Keep track of every row in a flat array
|
||||
flatRows.push(row)
|
||||
// Also keep track of every row by its ID
|
||||
rowsById[id] = row
|
||||
|
||||
// Get the original subrows
|
||||
row.originalSubRows = getSubRows(originalRow, rowIndex)
|
||||
|
||||
// Then recursively access them
|
||||
if (row.originalSubRows) {
|
||||
const subRows = []
|
||||
row.originalSubRows.forEach((d, i) =>
|
||||
accessRow(d, i, depth + 1, row, subRows)
|
||||
)
|
||||
// Keep the new subRows array on the row
|
||||
row.subRows = subRows
|
||||
}
|
||||
} else if (row.subRows) {
|
||||
// If the row exists, then it's already been accessed
|
||||
// Keep recursing, but don't worry about passing the
|
||||
// accumlator array (those rows already exist)
|
||||
row.originalSubRows.forEach((d, i) => accessRow(d, i, depth + 1, row))
|
||||
}
|
||||
|
||||
// If the column has an accessor, use it to get a value
|
||||
if (column.accessor) {
|
||||
row.values[column.id] = column.accessor(originalRow, rowIndex, row)
|
||||
}
|
||||
|
||||
// Allow plugins to manipulate the column value
|
||||
row.values[column.id] = reduceHooks(
|
||||
accessValueHooks,
|
||||
row.values[column.id],
|
||||
{
|
||||
row,
|
||||
column,
|
||||
instance: getInstance(),
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
data.forEach((originalRow, rowIndex) =>
|
||||
accessRow(originalRow, rowIndex, 0, undefined, rows)
|
||||
)
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
export function makeHeaderGroups(flatColumns, defaultColumn) {
|
||||
export function makeHeaderGroups(allColumns, defaultColumn) {
|
||||
const headerGroups = []
|
||||
|
||||
// Build each header group from the bottom up
|
||||
const buildGroup = (columns, depth) => {
|
||||
let scanColumns = allColumns
|
||||
|
||||
let uid = 0
|
||||
const getUID = () => uid++
|
||||
|
||||
while (scanColumns.length) {
|
||||
// The header group we are creating
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
}
|
||||
|
||||
// The parent columns we're going to scan next
|
||||
const parentColumns = []
|
||||
|
||||
// Do any of these columns have parents?
|
||||
const hasParents = columns.some(col => col.parent)
|
||||
|
||||
columns.forEach(column => {
|
||||
// Are we the first column in this group?
|
||||
const isFirst = !parentColumns.length
|
||||
const hasParents = scanColumns.some(d => d.parent)
|
||||
|
||||
// Scan each column for parents
|
||||
scanColumns.forEach(column => {
|
||||
// What is the latest (last) parent column?
|
||||
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||
|
||||
// If the column has a parent, add it if necessary
|
||||
if (column.parent) {
|
||||
const similarParentColumns = parentColumns.filter(
|
||||
d => d.originalId === column.parent.id
|
||||
)
|
||||
if (isFirst || latestParentColumn.originalId !== column.parent.id) {
|
||||
parentColumns.push({
|
||||
let newParent
|
||||
|
||||
if (hasParents) {
|
||||
// If the column has a parent, add it if necessary
|
||||
if (column.parent) {
|
||||
newParent = {
|
||||
...column.parent,
|
||||
originalId: column.parent.id,
|
||||
id: [column.parent.id, similarParentColumns.length].join('_'),
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, we'll need to add a place holder if necessary
|
||||
const originalId = [column.id, 'placeholder'].join('_')
|
||||
const similarParentColumns = parentColumns.filter(
|
||||
d => d.originalId === originalId
|
||||
)
|
||||
const placeholderColumn = decorateColumn(
|
||||
{
|
||||
originalId,
|
||||
id: [column.id, 'placeholder', similarParentColumns.length].join(
|
||||
'_'
|
||||
),
|
||||
placeholderOf: column,
|
||||
},
|
||||
defaultColumn
|
||||
)
|
||||
if (
|
||||
isFirst ||
|
||||
latestParentColumn.originalId !== placeholderColumn.originalId
|
||||
) {
|
||||
parentColumns.push(placeholderColumn)
|
||||
}
|
||||
}
|
||||
|
||||
// Establish the new headers[] relationship on the parent
|
||||
if (column.parent || hasParents) {
|
||||
latestParentColumn = [...parentColumns].reverse()[0]
|
||||
latestParentColumn.headers = latestParentColumn.headers || []
|
||||
if (!latestParentColumn.headers.includes(column)) {
|
||||
latestParentColumn.headers.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
column.totalHeaderCount = column.headers
|
||||
? column.headers.reduce(
|
||||
(sum, header) => sum + header.totalHeaderCount,
|
||||
0
|
||||
id: `${column.parent.id}_${getUID()}`,
|
||||
headers: [column],
|
||||
}
|
||||
} else {
|
||||
// If other columns have parents, we'll need to add a place holder if necessary
|
||||
const originalId = `${column.id}_placeholder`
|
||||
newParent = decorateColumn(
|
||||
{
|
||||
originalId,
|
||||
id: `${column.id}_placeholder_${getUID()}`,
|
||||
placeholderOf: column,
|
||||
headers: [column],
|
||||
},
|
||||
defaultColumn
|
||||
)
|
||||
: 1 // Leaf node columns take up at least one count
|
||||
}
|
||||
|
||||
// If the resulting parent columns are the same, just add
|
||||
// the column and increment the header span
|
||||
if (
|
||||
latestParentColumn &&
|
||||
latestParentColumn.originalId === newParent.originalId
|
||||
) {
|
||||
latestParentColumn.headers.push(column)
|
||||
} else {
|
||||
parentColumns.push(newParent)
|
||||
}
|
||||
}
|
||||
|
||||
headerGroup.headers.push(column)
|
||||
})
|
||||
|
||||
headerGroups.push(headerGroup)
|
||||
|
||||
if (parentColumns.length) {
|
||||
buildGroup(parentColumns, depth + 1)
|
||||
}
|
||||
// Start scanning the parent columns
|
||||
scanColumns = parentColumns
|
||||
}
|
||||
|
||||
buildGroup(flatColumns, 0)
|
||||
|
||||
return headerGroups.reverse()
|
||||
}
|
||||
|
||||
@ -225,20 +318,20 @@ export function isFunction(a) {
|
||||
}
|
||||
}
|
||||
|
||||
export function flattenBy(columns, childKey) {
|
||||
export function flattenBy(arr, key) {
|
||||
const flatColumns = []
|
||||
|
||||
const recurse = columns => {
|
||||
columns.forEach(d => {
|
||||
if (!d[childKey]) {
|
||||
const recurse = arr => {
|
||||
arr.forEach(d => {
|
||||
if (!d[key]) {
|
||||
flatColumns.push(d)
|
||||
} else {
|
||||
recurse(d[childKey])
|
||||
recurse(d[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
recurse(columns)
|
||||
recurse(arr)
|
||||
|
||||
return flatColumns
|
||||
}
|
||||
@ -280,6 +373,12 @@ export function shouldAutoRemoveFilter(autoRemove, value) {
|
||||
return autoRemove ? autoRemove(value) : typeof value === 'undefined'
|
||||
}
|
||||
|
||||
export function unpreparedAccessWarning() {
|
||||
throw new Error(
|
||||
'React-Table: You have not called prepareRow(row) one or more rows you are attempting to render.'
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const reOpenBracket = /\[/g
|
||||
|
||||
41
test-utils/makeTestData.js
Normal file
41
test-utils/makeTestData.js
Normal file
@ -0,0 +1,41 @@
|
||||
const range = len => {
|
||||
const arr = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const newPerson = (depth, index, total) => {
|
||||
const age = (depth + 1) * (index + 1)
|
||||
const visits = age * 10
|
||||
const progress = (index + 1) / total
|
||||
|
||||
return {
|
||||
firstName: `${depth} ${index} firstName`,
|
||||
lastName: `${depth} ${index} lastName`,
|
||||
age,
|
||||
visits,
|
||||
progress,
|
||||
status:
|
||||
progress > 0.66
|
||||
? 'relationship'
|
||||
: progress > 0.33
|
||||
? 'complicated'
|
||||
: 'single',
|
||||
}
|
||||
}
|
||||
|
||||
export default function makeTestData(...lens) {
|
||||
const makeDataLevel = (depth = 0) => {
|
||||
const len = lens[depth]
|
||||
return range(len).map(d => {
|
||||
return {
|
||||
...newPerson(depth, d, len),
|
||||
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return makeDataLevel()
|
||||
}
|
||||
31
test-utils/react-testing.js
vendored
Normal file
31
test-utils/react-testing.js
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { render as originalRender } from '@testing-library/react'
|
||||
import diff from 'jest-diff'
|
||||
import chalk from 'chalk'
|
||||
|
||||
const render = (...args) => {
|
||||
const rendered = originalRender(...args)
|
||||
|
||||
rendered.lastFragment = new DocumentFragment()
|
||||
|
||||
rendered.debugDiff = (log = true) => {
|
||||
const nextFragment = rendered.asFragment()
|
||||
|
||||
if (log) {
|
||||
console.log(
|
||||
diff(rendered.lastFragment, nextFragment, {
|
||||
aAnnotation: 'Previous',
|
||||
bAnnotation: 'Next',
|
||||
aColor: chalk.red,
|
||||
bColor: chalk.green,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
rendered.lastFragment = nextFragment
|
||||
}
|
||||
return rendered
|
||||
}
|
||||
|
||||
export * from '@testing-library/react'
|
||||
|
||||
export { render }
|
||||
Loading…
Reference in New Issue
Block a user